Skip to content

Latest commit

History

74 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Welcome to PocketBase Experiment!

PocketBase-Experiment

Reference for this experiment:https://www.youtube.com/watch?v=rediFff54JA&t=75s
This experiment was initiated on April 9, 2023.

Table of Contents

Introduction to PocketBase

PocketBase is similar to Firebase (a Google service) and Supabase (an open-source Firebase alternative), which handle backend functions like file upload, saving data to a database, etc.

I know that my introduction to PocketBase was too short to describe it, so here is the link to a video of Fireship where he fully introduces PocketBase.

You can download PocketBase using this link.

Back to top

How to Run PocketBase?

Once you've downloaded PocketBase and it was now in Downloads, do the following:
1. Extract the ZIP file that you downloaded.
2. Rename it PocketBase or however you like, but keep it short.
3. Go to that directory, e.g., Downloads/PocketBase.
4. Now enter this command:

./pocketbase serve

If the command above didn't work for you, try this:

pocketbase serve

Note: The pocketbase you see in the command is not the folder name, it was the keyword for the command.

5. After running the previous command you will see something like this:

2023/04/0922:11:46Serverstartedat http://127.0.0.1:8090REST API: http://127.0.0.1:8090/api/AdminUI: http://127.0.0.1:8090/_/

Back to top

Explaining the Links

Based on the official documentation, the meaning of the links are the following:
http://127.0.0.1:8090 - if pb_public directory exists, serves the static content from it (html, css, images, etc.)
http://127.0.0.1:8090/_/ - Admin dashboard UI
http://127.0.0.1:8090/api/ - REST API

Back to top

Folders in a Pre-Built Pocket Base

The prebuilt PocketBase executable will automatically create and manage 2 new directories alongside the executable:
pb_data - stores your application data, uploaded files, etc. (usually should be added in .gitignore).
pb_migrations - contains JS migration files with your collection changes (can be safely committed in your repository).

Back to top

User-Defined Folder

You have to add the following folder:
pb_public - This is the folder that will be hosted by PocketBase. Here is an example:
1. After creating the pb_public folder, create a file inside this folder named index.html and copy and paste the following code:

<!DOCTYPE html><htmllang="en"><head><metacharset="UTF-8"><metahttp-equiv="X-UA-Compatible" content="IE=edge"><metaname="viewport" content="width=device-width, initial-scale=1.0"><title>Testing123</title></head><body><h1>Welcome to PocketBase Experiment by MadriñanComLab!</h1></body></html>

2. Then go to this URL:

http://127.0.0.1:8090/

Back to top

Adding a React Application to a PocketBase App:

To add the React app, just do the following command:

npm create react-app <app_name>

Example:

npm create react-app pb_app

Back to top

Regaining Access to the PocketBase Admin UI

Just in case you have forgotten your password. What you can do is:
1. Stop the server of PocketBase on your computer (if it is running).
2. Delete the pb_data
3. Run your PocketBase
4. Go to http://127.0.0.1:8090/_/
5. Then you can now set your new email and password.

Back to top

Dangers of Deleting pb_data

Deleting this folder was helpful when you forgot your password for the admin UI, but deleting it will also affect the configuration you set in your PocketBase application.

Later on, configuring the mail settings of PocketBase will be discussed in this documentation.

Additional Note: By deleting the pb_data, you are deleting the data in your database. DO NOT delete pb_migration because this contains the structure of your database. You may delete it unless you intend to delete the structure of the database. But to just regain access to the admin side of your database, just delete the pb_data.

Back to top

Creating a New Collection

The admin UI was straightforward, but in case you are hesitant, here is a brief introduction: Steps-1-to-31. Start with creating your collection. After you click that, a modal will appear on the right.
2. Name your collection however you want. Think of a collection as a table, and you can see in the suggestion that it follows the naming convention of SQL (where tables are named plural).
3. After naming your first collection, you can then create the first field in your collection.

Steps-4-to-54. After clicking the New field button, options for data type would appear.
5. Name your first field here. Think of a field as a column in your database.
Steps-66. If you click the cog icon inline with your field, an option for min and max length would appear as well as the regex.
Steps-7PB-Index-Modal7. If you click "New index," a modal will appear for you to setup your indexes.

Unfamiliar with indexes? You can read about it here

8. You can now save your first collection. Ow, wait, you aren't able to save it? Well, that was because there is already a "users" collection that was initially included in your first PocketBase application.

I apologize about it; this README was created while first exploring PocketBase.

Back to top

React and PocketBase SDK Setup

In the tutorial, the following files were added to the root folder of pb_app:
.env.local
jsconfig.json - In the code here, what it does is make the src folder the root directory for importing and avoid the example imports below.:

importxfrom"../../../folder/file";

Delete the following in pb_app/src:
App.css
App.test.js
index.css
logo.svg
reportWebVitals.js
setupTest.js
Remove the following in index.js:

importreportWebVitalsfrom'./reportWebVitals';import'./index.css';
...
// If you want to start measuring performance in your app, pass a function// to log results (for example: reportWebVitals(console.log))// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitalsreportWebVitals();

Remove the following in App.js:

importlogofrom'./logo.svg';import'./App.css';
...
// This is the chuck of code in return statement<divclassName="App"><headerclassName="App-header"><imgsrc={logo}className="App-logo"alt="logo"/><p>
Edit <code>src/App.js</code> and save to reload.
</p><aclassName="App-link"href="https://reactjs.org"target="_blank"rel="noopener noreferrer">
Learn React
</a></header></div>

You can now add the JavaScript PocketBase SDK by running the following command in your terminal:

npm install pocketbase --save

Note: Make sure you are in the pb_app directory, where the React app is.

Then create the /lib/pocketbase.js file inside the src folder and add the following code snippet:

importPocketBasefrom"pocketbase";constpb=newPocketBase(process.env.REACT_APP_PB_URL);exportdefaultpb;

Note: The value of REACT_APP_PB_URL was http://127.0.0.1:8090, and it was in the .env.local

Now, to test if everything is working, run the following command:

npm start

Back to top

Authentication (with react-hook-form)

PocketBase provides documentation about their authentication; you can read it here if you want to read more about it.

Examples

The following will be a set of different examples of PocketBase authentication.

#1

First, create Auth.js in src. For now, the Auth component will contain the following:

importPBfrom"lib/pocketbase";functionAuth(){return(<>{/* For a simple example of pocketbase authentication */}<h1>Logged In: {PB.authStore.isValid.toString()}</h1></>);}exportdefaultAuth;

Import the Auth component to App.js:

importAuthfrom"Auth";functionApp(){return(<><Auth/></>);}exportdefaultApp;

Your browser should display something like this: ss-3

The first example makes sense that it displays false because a user hasn't logged in yet, right?

#2

Now, let's level up the previous example. Still in Auth.js, do the following:

importPBfrom"lib/pocketbase";import{useState}from"react";import{useForm}from"react-hook-form";functionAuth(){const{ register, handleSubmit }=useForm();const[is_loading,setLoading]=useState(false);asyncfunctionlogin(data){/* This function was a custom function of handleSubmit() */setLoading(true);try{constauth_data=awaitPB.collection("users").authWithPassword(data.email,data.password);}catch(error){console.log(error);}setLoading(false);}return(<><h1>Logged In: {PB.authStore.isValid.toString()}</h1>{is_loading&&<p>Loading...</p>}<formonSubmit={handleSubmit(login)}><inputtype="text"placeholder="email"{...register("email")}/><inputtype="password"placeholder="password"{...register("password")}/><buttontype="submit"disabled={is_loading}>Login</button></form></>);}exportdefaultAuth;

Before we proceed with creating the first user, let's breakdown the example code above because there was a huge change between examples #1 and #2.

  • First is about the React-hook-form, which will help us handle the sample login:
const{ register, handleSubmit }=useForm();

To install react-hook-form in your project, just run the following command:

npm install react-hook-form

Note: Make sure you are in pb_app folder before you install this npm library.

  • State object that will be used for a simple loading indicator.
const[is_loading,setLoading]=useState(false);

  • Then, on the actual code for validation, there was a comment on the code to help break it down:
asyncfunctionlogin(data){/* This function was a custom function of handleSubmit() *//* Set is_loading to true to indicated the request in on process */setLoading(true);/* Try catch statement was used to handle the possible error that may occur. */try{constauth_data=awaitPB/* The 'collection' is the collection of pocketbase */.collection("users")/* 'authWithPassword' will handle the authentication */.authWithPassword(data.email,data.password);}catch(error){console.log(error);}setLoading(false);}

Now, let's create our first user. You can use whatever email and password you want: sample-usersample-user(1)

After this, you can try it to see if it works.

#3

Users can now log in, and for the last example, we will enable users to log out. Do this in Auth.js.:

importPBfrom"lib/pocketbase";import{useState,useEffect}from"react";import{useForm}from"react-hook-form";functionAuth(){const{ register, handleSubmit }=useForm();const[is_loading,setLoading]=useState(false);constis_logged_in=PB.authStore.isValid;/* Later, this will be use to re-render the Auth component */const[dummy,setDummy]=useState(0);asyncfunctionlogin(data){/* This function was a custom function of handleSubmit() */setLoading(true);try{constauth_data=awaitPB.collection("users").authWithPassword(data.email,data.password);}catch(error){console.log(error);}setLoading(false);}functionlogout(){/* logout() function was not an asynchronous function because it doesn't do an API call. Instead, it simply clear the cookie */PB.authStore.clear();setDummy(Math.random());}/* Just another way of conditional rendering in React */if(is_logged_in){return(<>{/* If a user has logged in, display the email address */}<h1>Logged In: {is_logged_in&&PB.authStore.model.email}</h1><buttononClick={logout}>Log Out</button></>);}return(<><h1>Welcome to Login Page!</h1>{is_loading&&<p>Loading...</p>}<formonSubmit={handleSubmit(login)}><inputtype="text"placeholder="email"{...register("email")}/><inputtype="password"placeholder="password"{...register("password")}/><buttontype="submit"disabled={is_loading}>Login</button></form></>);}exportdefaultAuth;

Back to top

Auth Hooks (with react-query)

In this part of the documentation, we will be destructuring the example code from previous chapter.

Destructuring Logout Function

First, we'll destructure the function for logging out. Create a hooks folder in src and create UseLogout.js, which will contain the following code:

importPBfrom"lib/pocketbase";import{useState}from"react";functionUseLogout(){/* Later, this will be use to re-render the Auth component */const[dummy,setDummy]=useState(0);functionlogout(){/* logout() function was not an asynchronous function because it doesn't do an API call. Instead, it simply clear the cookie */PB.authStore.clear();setDummy(Math.random());}returnlogout;}exportdefaultUseLogout;

Note: The code above was an example of React custom hooks.


The Auth.js will look like this:

/* The custom hook was imported to Auth.js */importUseLogoutfrom"hooks/UseLogout";importPBfrom"lib/pocketbase";import{useState,useEffect}from"react";import{useForm}from"react-hook-form";functionAuth(){/* logout() function was declared like this */constlogout=UseLogout();const{ register, handleSubmit }=useForm();const[is_loading,setLoading]=useState(false);constis_logged_in=PB.authStore.isValid;asyncfunctionlogin(data){/* This function was a custom function of handleSubmit() */setLoading(true);try{constauth_data=awaitPB.collection("users").authWithPassword(data.email,data.password);}catch(error){console.log(error);}setLoading(false);}if(is_logged_in){return(<>{/* If a user has logged in, display the email address */}<h1>Logged In: {is_logged_in&&PB.authStore.model.email}</h1><buttononClick={logout}>Log Out</button></>);}return(<><h1>Welcome to Login Page!</h1>{is_loading&&<p>Loading...</p>}<formonSubmit={handleSubmit(login)}><inputtype="text"placeholder="email"{...register("email")}/><inputtype="password"placeholder="password"{...register("password")}/><buttontype="submit"disabled={is_loading}>Login</button></form></>);}exportdefaultAuth;

If you try this, it will work well, but the same problem as in the previous chapter will remain: the credentials you've inputted will remain.

To resolve this, add reset in the destructuring of useForm():

const{ register, handleSubmit, reset }=useForm();

Then, add this reset() function to the login function:

asyncfunctionlogin(data){/* This function was a custom function of handleSubmit() */setLoading(true);try{constauth_data=awaitPB.collection("users").authWithPassword(data.email,data.password);}catch(error){console.log(error);}setLoading(false);/* reset() was added here */reset();}

Destructuring Login Function

Now that we've destructured the logout function using a custom hook, we'll take a few steps to destructure the login as well.

Create UseLogin.js and it will contain the following:

importPBfrom"lib/pocketbase";import{useState}from"react";functionUseLogin(){const[is_loading,setLoading]=useState(false);asyncfunctionlogin({ email, password }){/* This function was a custom function of handleSubmit() */setLoading(true);try{constauth_data=awaitPB.collection("users").authWithPassword(email,password);}catch(error){console.log(error);}setLoading(false);}return{ login, is_loading };}exportdefaultUseLogin;

Since we removed the login function, Auth.js will look like this:

importUseLoginfrom"hooks/UseLogin";importUseLogoutfrom"hooks/UseLogout";importPBfrom"lib/pocketbase";import{useForm}from"react-hook-form";functionAuth(){constlogout=UseLogout();const{ login, is_loading }=UseLogin();const{ register, handleSubmit, reset }=useForm();constis_logged_in=PB.authStore.isValid;asyncfunctiononSubmit(data){login(data);reset();}if(is_logged_in){return(<>{/* If a user has logged in, display the email address */}<h1>Logged In: {is_logged_in&&PB.authStore.model.email}</h1><buttononClick={logout}>Log Out</button></>);}return(<><h1>Welcome to Login Page!</h1>{is_loading&&<p>Loading...</p>}<formonSubmit={handleSubmit(onSubmit)}><inputtype="text"placeholder="email"{...register("email")}/><inputtype="password"placeholder="password"{...register("password")}/><buttontype="submit"disabled={is_loading}>Login</button></form></>);}exportdefaultAuth;

Destructuring API call to PocketBase

In this part of the documentation, we will be using the react-query, and to install this library, run this command:

npm install react-query

Before we proceed to destructuring, we have to do the following on App.js to be able to implement the react-query and do the destructuring:

importAuthfrom"Auth";/* Import the necessary functions */import{QueryClientProvider,QueryClient}from"react-query";/* Initialize the QueryClient */constqueryClient=newQueryClient();functionApp(){return({/* Wrap the Auth component with QueryClientProvider and use queryClient as client */}<QueryClientProviderclient={ queryClient }><Auth/></QueryClientProvider>);}exportdefaultApp;

UseLogin.js will look like this after the destructuring:

importPBfrom"lib/pocketbase";/* useMutation was imported */import{useMutation}from"react-query"/* useState for is_loading was removed because react-query has built-in and it was isLoading*/functionUseLogin(){/* Because login() function was used as argument to useMutation, we no longer need to wrap it with try catch */asyncfunctionlogin({ email, password }){/* This function was a custom function of handleSubmit() */constauth_data=awaitPB.collection("users").authWithPassword(email,password);}/* login() was passed in useMutation as argument */returnuseMutation(login)}exportdefaultUseLogin;

And because of these changes, Auth.js will look like this:

importUseLoginfrom"hooks/UseLogin";importUseLogoutfrom"hooks/UseLogout";importPBfrom"lib/pocketbase";import{useForm}from"react-hook-form";functionAuth(){constlogout=UseLogout();const{/* "mutate" will be used to invoke the login() function */
mutate,/* "is_loading" is now isLoading() which is available to useMutation() by default */
isLoading,/* isError was also available to useMutation() by default */
isError }=UseLogin();const{ register, handleSubmit, reset }=useForm();constis_logged_in=PB.authStore.isValid;asyncfunctiononSubmit(data){mutate(data);reset();}if(is_logged_in){return(<>{/* If a user has logged in, display the email address */}<h1>Logged In: {is_logged_in&&PB.authStore.model.email}</h1><buttononClick={logout}>Log Out</button></>);}return(<><h1>Welcome to Login Page!</h1>{isLoading&&<p>Loading...</p>}{isError&&<p>Invalid email or password</p>}<formonSubmit={handleSubmit(onSubmit)}><inputtype="text"placeholder="email"{...register("email")}/><inputtype="password"placeholder="password"{...register("password")}/><buttontype="submit"disabled={isLoading}>Login</button></form></>);}exportdefaultAuth;

If you want to read more about react-query, here is the official documentation.

Back to top

Sending Verification Emails with Brevo (formerly Sendinblue) SMTP

In this part of the experiment, we will be using Sendinblue to send emails. If you don't have account in 'Sendinblue', you may create your own in this link.

In this part of the experiment, we will be using Sendinblue to send emails. If you don't have an account at 'Sendinblue', you may create one at this link.

Setting up your Sendinblue Account

Once you are done creating your own account, you should see this page: image


Now, click Transactional and then Settings in the left navigation bar. You should be able to see this page: image


Once you are there, click Configuration and Get Your SMTP Key. You should be on this page. Sendinblue


Now, click Generate a new SMTP key. A modal should appear, and you may enter any key you want. In my case, I use lab_exp. Then click the Generate button.

After you click the Generate button, the SMTP key of Sendinblue should appear; copy that and don't lose it.

Setting up an SMTP Server in PocketBase

To set up SMTP in PocketBase, go to the admin UI home page. Then click the setting icon and click on Mail Settings. You should be able to see this page:

image


Now, toggle Use SMTP mail server (recommended). A user interface for configuring your SMTP mail server would appear. Remember the SMTP key you copied earlier? You'll be pasting that in the password field. image


For the username field, go back to Sendinblue and close the modal for the SMTP key. Copy the login: Sendinblue(1)


For the SMTP Server Host field, paste smtp-relay.sendinblue.com, which can be seen back on the Sendinblue page. Then you may now click Save changes.

Send test email will appear, and when you click that, you can test if the configuration you made is working. Sendinblue(2)

Error in Setting up PocketBase Mail Settings

This is an optional part of the documentation in case you encountered an error while setting up the mail settings of PocketBase.

During the experiment at the lab, I encounter this error: pb-error

To resolve this issue, delete the following files:

  • CHANGELOG.md
  • LICENSE.md
  • pocketbase.exe
  • pb_data

Since you deleted the pb_data. You have to repeat the configuration of PocketBase mail settings and create an admin account for the Admin UI. You may read the Dangers of Deleting pb_data included in this documentation.

PocketBase Collection Types

In PocketBase, there are two types of collections:

  • Base Collection - is the default collection type, and you can use it for any type of data.
  • Auth Collection - contains extra fields to manage users, like username, email, and verified. An example of an Auth collection is the PocketBase default user collections.

This was discussed because later we will be creating a simple way of authenticating users.

Source:https://www.makeuseof.com/pocketbase-what-and-how/#:~:text=Creating%20a%20Collection%20in%20PocketBase&text=A%20collection%20can%20either%20be,username%2C%20email%2C%20and%20verified.

Getting the Records

In getting the records into PocketBase, here is an example from the official documentation:

importPocketBasefrom'pocketbase';constpb=newPocketBase('http://127.0.0.1:8090');
...
constrecord1=awaitpb.collection('posts').getOne('RECORD_ID',{expand: 'relField1,relField2.subRelField',});

This is how we implement it in hooks/UseVerified.js:

constuser_id=PB.authStore.model.id;constuser_data=awaitPB.collection("users").getOne(user_id);setIsVerified(user_data.verified);

Sending the Email Verification

To start off, we need the following:

  • First, a button that will send email verification
  • Then, a function will handle the click event for sending email verification.
  • Lastly, an indicator of whether the user is verified or not

Earlier, UseVerified.js was mentioned; add the following function to that file:

asyncfunctionrequestVerification(){constemail=PB.authStore.model.email;constresponse=awaitPB.collection("users").requestVerification(email);if(response){alert("Verification email sent! Check your inbox.");}}

Now, we're done with the function that will handle the sending of emails. In Auth.js, import the UseVerified custom hook:

importUseVerifiedfrom"hooks/UseVerified";
...
const{ is_verified, requestVerification }=UseVerified();

Now, create the indicator and the button in Auth.js:

<p>Verified: {is_verified.toString()}</p>{!is_verified&&<buttononClick={requestVerification}>Send Verification</button>}

Your UseVerified.js should look like the following:

import{useState,useEffect}from"react";importPBfrom"lib/pocketbase";functionUseVerified(){const[is_verified,setIsVerified]=useState(false);useEffect(()=>{asyncfunctioncheckVerified(){constuser_id=PB.authStore.model.id;constuser_data=awaitPB.collection("users").getOne(user_id);console.log("Fetch data: ",user_data);setIsVerified(user_data.verified);}constis_logged_in=PB.authStore.isValid;/* Invoke checkVerified() function if the user is logged in */if(is_logged_in){checkVerified();}},[]);asyncfunctionrequestVerification(){constemail=PB.authStore.model.email;constresponse=awaitPB.collection("users").requestVerification(email);if(response){alert("Verification email sent! Check your inbox.");}}return{ is_verified, requestVerification };}exportdefaultUseVerified;

In testing it, you should use your email account, and you should be able to receive this email once you test it: Verified

Click the Verify button in the email, return to your React app, and refresh it. Verified should have a value of true now.

Back to top

Automatic Re-fetching with the useQuery Hook

In this chapter of the documentation, we will be making changes to the app from the previous chapter and enabling it to reflect the changes that are happening in the database (PocketBase).

In order to do this, we will be using useQuery in React, and you may visit the official documentation by clicking this.

There will be a huge changes in UseVerified.js, first import the useQuery:

There will be huge changes in UseVerified.js. First, import useQuery.

import{useQuery}from"react-query";

Then UseVerified() will have a different return value:

returnuseQuery({queryFn: checkVerified,queryKey: ["check-verified",user_id]});

This is the last chapter in this documentation; you can click this to see the whole change in UseVerified.js. There were minor changes in Auth.js as well; you can click [this](https://github.com/MadrinanComLab/Exp-PocketBase/blob/master/pb_app/src/components/Auth.js to see the changes.

Explaining the Attributes of the useQuery Option

Keys in the option of useQuery was case sensitive. For example, you cannot make queryFn into queryFunction or other keys that you may wish to use.

The object that is used as an argument is called options, and each of its attributes has a different purpose:

  • queryFn - The function you will assign here will be responsible for fetching the data you need.

Note: Since checkVerified was used to be the value of queryFunction, the return value of it will change:

returnuser_data.verified
  • queryKey - This will be used for data caching, and the value of "check-verified" will be the key, and user_id is the second argument, which in this case will be used to query the user by its record id.

Note: The value of user_id will come from the code snippet below. Remember, this will have a value when you log in a user, then it becomes undefined when no user is logged in, and that is why optional chaining was implemented.

...
exportdefaultfunctionUseVerified(){constuser_id=PB.authStore.model?.id;
...
}

Back to top


Did You Like This Experiment?

Stay tuned for upcoming projects and experiments by following me on the following accounts:

About

This repository is an experiment that aims to learn about PocketBase and provide helpful notes to the lab and other developers who want to use PocketBase as a beginner.

Topics

Resources

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
GitHub - MadrinanComLab/Exp-PocketBase: This repository is an experiment that aims to learn about PocketBase and provide helpful notes to the lab and other developers who want to use PocketBase as a beginner. · GitHub
Skip to content

Latest commit

History

74 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Welcome to PocketBase Experiment!

PocketBase-Experiment

Reference for this experiment:https://www.youtube.com/watch?v=rediFff54JA&t=75s
This experiment was initiated on April 9, 2023.

Table of Contents

Introduction to PocketBase

PocketBase is similar to Firebase (a Google service) and Supabase (an open-source Firebase alternative), which handle backend functions like file upload, saving data to a database, etc.

I know that my introduction to PocketBase was too short to describe it, so here is the link to a video of Fireship where he fully introduces PocketBase.

You can download PocketBase using this link.

Back to top

How to Run PocketBase?

Once you've downloaded PocketBase and it was now in Downloads, do the following:
1. Extract the ZIP file that you downloaded.
2. Rename it PocketBase or however you like, but keep it short.
3. Go to that directory, e.g., Downloads/PocketBase.
4. Now enter this command:

./pocketbase serve

If the command above didn't work for you, try this:

pocketbase serve

Note: The pocketbase you see in the command is not the folder name, it was the keyword for the command.

5. After running the previous command you will see something like this:

2023/04/0922:11:46Serverstartedat http://127.0.0.1:8090REST API: http://127.0.0.1:8090/api/AdminUI: http://127.0.0.1:8090/_/

Back to top

Explaining the Links

Based on the official documentation, the meaning of the links are the following:
http://127.0.0.1:8090 - if pb_public directory exists, serves the static content from it (html, css, images, etc.)
http://127.0.0.1:8090/_/ - Admin dashboard UI
http://127.0.0.1:8090/api/ - REST API

Back to top

Folders in a Pre-Built Pocket Base

The prebuilt PocketBase executable will automatically create and manage 2 new directories alongside the executable:
pb_data - stores your application data, uploaded files, etc. (usually should be added in .gitignore).
pb_migrations - contains JS migration files with your collection changes (can be safely committed in your repository).

Back to top

User-Defined Folder

You have to add the following folder:
pb_public - This is the folder that will be hosted by PocketBase. Here is an example:
1. After creating the pb_public folder, create a file inside this folder named index.html and copy and paste the following code:

<!DOCTYPE html><htmllang="en"><head><metacharset="UTF-8"><metahttp-equiv="X-UA-Compatible" content="IE=edge"><metaname="viewport" content="width=device-width, initial-scale=1.0"><title>Testing123</title></head><body><h1>Welcome to PocketBase Experiment by MadriñanComLab!</h1></body></html>

2. Then go to this URL:

http://127.0.0.1:8090/

Back to top

Adding a React Application to a PocketBase App:

To add the React app, just do the following command:

npm create react-app <app_name>

Example:

npm create react-app pb_app

Back to top

Regaining Access to the PocketBase Admin UI

Just in case you have forgotten your password. What you can do is:
1. Stop the server of PocketBase on your computer (if it is running).
2. Delete the pb_data
3. Run your PocketBase
4. Go to http://127.0.0.1:8090/_/
5. Then you can now set your new email and password.

Back to top

Dangers of Deleting pb_data

Deleting this folder was helpful when you forgot your password for the admin UI, but deleting it will also affect the configuration you set in your PocketBase application.

Later on, configuring the mail settings of PocketBase will be discussed in this documentation.

Additional Note: By deleting the pb_data, you are deleting the data in your database. DO NOT delete pb_migration because this contains the structure of your database. You may delete it unless you intend to delete the structure of the database. But to just regain access to the admin side of your database, just delete the pb_data.

Back to top

Creating a New Collection

The admin UI was straightforward, but in case you are hesitant, here is a brief introduction: Steps-1-to-31. Start with creating your collection. After you click that, a modal will appear on the right.
2. Name your collection however you want. Think of a collection as a table, and you can see in the suggestion that it follows the naming convention of SQL (where tables are named plural).
3. After naming your first collection, you can then create the first field in your collection.

Steps-4-to-54. After clicking the New field button, options for data type would appear.
5. Name your first field here. Think of a field as a column in your database.
Steps-66. If you click the cog icon inline with your field, an option for min and max length would appear as well as the regex.
Steps-7PB-Index-Modal7. If you click "New index," a modal will appear for you to setup your indexes.

Unfamiliar with indexes? You can read about it here

8. You can now save your first collection. Ow, wait, you aren't able to save it? Well, that was because there is already a "users" collection that was initially included in your first PocketBase application.

I apologize about it; this README was created while first exploring PocketBase.

Back to top

React and PocketBase SDK Setup

In the tutorial, the following files were added to the root folder of pb_app:
.env.local
jsconfig.json - In the code here, what it does is make the src folder the root directory for importing and avoid the example imports below.:

importxfrom"../../../folder/file";

Delete the following in pb_app/src:
App.css
App.test.js
index.css
logo.svg
reportWebVitals.js
setupTest.js
Remove the following in index.js:

importreportWebVitalsfrom'./reportWebVitals';import'./index.css';
...
// If you want to start measuring performance in your app, pass a function// to log results (for example: reportWebVitals(console.log))// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitalsreportWebVitals();

Remove the following in App.js:

importlogofrom'./logo.svg';import'./App.css';
...
// This is the chuck of code in return statement<divclassName="App"><headerclassName="App-header"><imgsrc={logo}className="App-logo"alt="logo"/><p>
Edit <code>src/App.js</code> and save to reload.
</p><aclassName="App-link"href="https://reactjs.org"target="_blank"rel="noopener noreferrer">
Learn React
</a></header></div>

You can now add the JavaScript PocketBase SDK by running the following command in your terminal:

npm install pocketbase --save

Note: Make sure you are in the pb_app directory, where the React app is.

Then create the /lib/pocketbase.js file inside the src folder and add the following code snippet:

importPocketBasefrom"pocketbase";constpb=newPocketBase(process.env.REACT_APP_PB_URL);exportdefaultpb;

Note: The value of REACT_APP_PB_URL was http://127.0.0.1:8090, and it was in the .env.local

Now, to test if everything is working, run the following command:

npm start

Back to top

Authentication (with react-hook-form)

PocketBase provides documentation about their authentication; you can read it here if you want to read more about it.

Examples

The following will be a set of different examples of PocketBase authentication.

#1

First, create Auth.js in src. For now, the Auth component will contain the following:

importPBfrom"lib/pocketbase";functionAuth(){return(<>{/* For a simple example of pocketbase authentication */}<h1>Logged In: {PB.authStore.isValid.toString()}</h1></>);}exportdefaultAuth;

Import the Auth component to App.js:

importAuthfrom"Auth";functionApp(){return(<><Auth/></>);}exportdefaultApp;

Your browser should display something like this: ss-3

The first example makes sense that it displays false because a user hasn't logged in yet, right?

#2

Now, let's level up the previous example. Still in Auth.js, do the following:

importPBfrom"lib/pocketbase";import{useState}from"react";import{useForm}from"react-hook-form";functionAuth(){const{ register, handleSubmit }=useForm();const[is_loading,setLoading]=useState(false);asyncfunctionlogin(data){/* This function was a custom function of handleSubmit() */setLoading(true);try{constauth_data=awaitPB.collection("users").authWithPassword(data.email,data.password);}catch(error){console.log(error);}setLoading(false);}return(<><h1>Logged In: {PB.authStore.isValid.toString()}</h1>{is_loading&&<p>Loading...</p>}<formonSubmit={handleSubmit(login)}><inputtype="text"placeholder="email"{...register("email")}/><inputtype="password"placeholder="password"{...register("password")}/><buttontype="submit"disabled={is_loading}>Login</button></form></>);}exportdefaultAuth;

Before we proceed with creating the first user, let's breakdown the example code above because there was a huge change between examples #1 and #2.

  • First is about the React-hook-form, which will help us handle the sample login:
const{ register, handleSubmit }=useForm();

To install react-hook-form in your project, just run the following command:

npm install react-hook-form

Note: Make sure you are in pb_app folder before you install this npm library.

  • State object that will be used for a simple loading indicator.
const[is_loading,setLoading]=useState(false);

  • Then, on the actual code for validation, there was a comment on the code to help break it down:
asyncfunctionlogin(data){/* This function was a custom function of handleSubmit() *//* Set is_loading to true to indicated the request in on process */setLoading(true);/* Try catch statement was used to handle the possible error that may occur. */try{constauth_data=awaitPB/* The 'collection' is the collection of pocketbase */.collection("users")/* 'authWithPassword' will handle the authentication */.authWithPassword(data.email,data.password);}catch(error){console.log(error);}setLoading(false);}

Now, let's create our first user. You can use whatever email and password you want: sample-usersample-user(1)

After this, you can try it to see if it works.

#3

Users can now log in, and for the last example, we will enable users to log out. Do this in Auth.js.:

importPBfrom"lib/pocketbase";import{useState,useEffect}from"react";import{useForm}from"react-hook-form";functionAuth(){const{ register, handleSubmit }=useForm();const[is_loading,setLoading]=useState(false);constis_logged_in=PB.authStore.isValid;/* Later, this will be use to re-render the Auth component */const[dummy,setDummy]=useState(0);asyncfunctionlogin(data){/* This function was a custom function of handleSubmit() */setLoading(true);try{constauth_data=awaitPB.collection("users").authWithPassword(data.email,data.password);}catch(error){console.log(error);}setLoading(false);}functionlogout(){/* logout() function was not an asynchronous function because it doesn't do an API call. Instead, it simply clear the cookie */PB.authStore.clear();setDummy(Math.random());}/* Just another way of conditional rendering in React */if(is_logged_in){return(<>{/* If a user has logged in, display the email address */}<h1>Logged In: {is_logged_in&&PB.authStore.model.email}</h1><buttononClick={logout}>Log Out</button></>);}return(<><h1>Welcome to Login Page!</h1>{is_loading&&<p>Loading...</p>}<formonSubmit={handleSubmit(login)}><inputtype="text"placeholder="email"{...register("email")}/><inputtype="password"placeholder="password"{...register("password")}/><buttontype="submit"disabled={is_loading}>Login</button></form></>);}exportdefaultAuth;

Back to top

Auth Hooks (with react-query)

In this part of the documentation, we will be destructuring the example code from previous chapter.

Destructuring Logout Function

First, we'll destructure the function for logging out. Create a hooks folder in src and create UseLogout.js, which will contain the following code:

importPBfrom"lib/pocketbase";import{useState}from"react";functionUseLogout(){/* Later, this will be use to re-render the Auth component */const[dummy,setDummy]=useState(0);functionlogout(){/* logout() function was not an asynchronous function because it doesn't do an API call. Instead, it simply clear the cookie */PB.authStore.clear();setDummy(Math.random());}returnlogout;}exportdefaultUseLogout;

Note: The code above was an example of React custom hooks.


The Auth.js will look like this:

/* The custom hook was imported to Auth.js */importUseLogoutfrom"hooks/UseLogout";importPBfrom"lib/pocketbase";import{useState,useEffect}from"react";import{useForm}from"react-hook-form";functionAuth(){/* logout() function was declared like this */constlogout=UseLogout();const{ register, handleSubmit }=useForm();const[is_loading,setLoading]=useState(false);constis_logged_in=PB.authStore.isValid;asyncfunctionlogin(data){/* This function was a custom function of handleSubmit() */setLoading(true);try{constauth_data=awaitPB.collection("users").authWithPassword(data.email,data.password);}catch(error){console.log(error);}setLoading(false);}if(is_logged_in){return(<>{/* If a user has logged in, display the email address */}<h1>Logged In: {is_logged_in&&PB.authStore.model.email}</h1><buttononClick={logout}>Log Out</button></>);}return(<><h1>Welcome to Login Page!</h1>{is_loading&&<p>Loading...</p>}<formonSubmit={handleSubmit(login)}><inputtype="text"placeholder="email"{...register("email")}/><inputtype="password"placeholder="password"{...register("password")}/><buttontype="submit"disabled={is_loading}>Login</button></form></>);}exportdefaultAuth;

If you try this, it will work well, but the same problem as in the previous chapter will remain: the credentials you've inputted will remain.

To resolve this, add reset in the destructuring of useForm():

const{ register, handleSubmit, reset }=useForm();

Then, add this reset() function to the login function:

asyncfunctionlogin(data){/* This function was a custom function of handleSubmit() */setLoading(true);try{constauth_data=awaitPB.collection("users").authWithPassword(data.email,data.password);}catch(error){console.log(error);}setLoading(false);/* reset() was added here */reset();}

Destructuring Login Function

Now that we've destructured the logout function using a custom hook, we'll take a few steps to destructure the login as well.

Create UseLogin.js and it will contain the following:

importPBfrom"lib/pocketbase";import{useState}from"react";functionUseLogin(){const[is_loading,setLoading]=useState(false);asyncfunctionlogin({ email, password }){/* This function was a custom function of handleSubmit() */setLoading(true);try{constauth_data=awaitPB.collection("users").authWithPassword(email,password);}catch(error){console.log(error);}setLoading(false);}return{ login, is_loading };}exportdefaultUseLogin;

Since we removed the login function, Auth.js will look like this:

importUseLoginfrom"hooks/UseLogin";importUseLogoutfrom"hooks/UseLogout";importPBfrom"lib/pocketbase";import{useForm}from"react-hook-form";functionAuth(){constlogout=UseLogout();const{ login, is_loading }=UseLogin();const{ register, handleSubmit, reset }=useForm();constis_logged_in=PB.authStore.isValid;asyncfunctiononSubmit(data){login(data);reset();}if(is_logged_in){return(<>{/* If a user has logged in, display the email address */}<h1>Logged In: {is_logged_in&&PB.authStore.model.email}</h1><buttononClick={logout}>Log Out</button></>);}return(<><h1>Welcome to Login Page!</h1>{is_loading&&<p>Loading...</p>}<formonSubmit={handleSubmit(onSubmit)}><inputtype="text"placeholder="email"{...register("email")}/><inputtype="password"placeholder="password"{...register("password")}/><buttontype="submit"disabled={is_loading}>Login</button></form></>);}exportdefaultAuth;

Destructuring API call to PocketBase

In this part of the documentation, we will be using the react-query, and to install this library, run this command:

npm install react-query

Before we proceed to destructuring, we have to do the following on App.js to be able to implement the react-query and do the destructuring:

importAuthfrom"Auth";/* Import the necessary functions */import{QueryClientProvider,QueryClient}from"react-query";/* Initialize the QueryClient */constqueryClient=newQueryClient();functionApp(){return({/* Wrap the Auth component with QueryClientProvider and use queryClient as client */}<QueryClientProviderclient={ queryClient }><Auth/></QueryClientProvider>);}exportdefaultApp;

UseLogin.js will look like this after the destructuring:

importPBfrom"lib/pocketbase";/* useMutation was imported */import{useMutation}from"react-query"/* useState for is_loading was removed because react-query has built-in and it was isLoading*/functionUseLogin(){/* Because login() function was used as argument to useMutation, we no longer need to wrap it with try catch */asyncfunctionlogin({ email, password }){/* This function was a custom function of handleSubmit() */constauth_data=awaitPB.collection("users").authWithPassword(email,password);}/* login() was passed in useMutation as argument */returnuseMutation(login)}exportdefaultUseLogin;

And because of these changes, Auth.js will look like this:

importUseLoginfrom"hooks/UseLogin";importUseLogoutfrom"hooks/UseLogout";importPBfrom"lib/pocketbase";import{useForm}from"react-hook-form";functionAuth(){constlogout=UseLogout();const{/* "mutate" will be used to invoke the login() function */
mutate,/* "is_loading" is now isLoading() which is available to useMutation() by default */
isLoading,/* isError was also available to useMutation() by default */
isError }=UseLogin();const{ register, handleSubmit, reset }=useForm();constis_logged_in=PB.authStore.isValid;asyncfunctiononSubmit(data){mutate(data);reset();}if(is_logged_in){return(<>{/* If a user has logged in, display the email address */}<h1>Logged In: {is_logged_in&&PB.authStore.model.email}</h1><buttononClick={logout}>Log Out</button></>);}return(<><h1>Welcome to Login Page!</h1>{isLoading&&<p>Loading...</p>}{isError&&<p>Invalid email or password</p>}<formonSubmit={handleSubmit(onSubmit)}><inputtype="text"placeholder="email"{...register("email")}/><inputtype="password"placeholder="password"{...register("password")}/><buttontype="submit"disabled={isLoading}>Login</button></form></>);}exportdefaultAuth;

If you want to read more about react-query, here is the official documentation.

Back to top

Sending Verification Emails with Brevo (formerly Sendinblue) SMTP

In this part of the experiment, we will be using Sendinblue to send emails. If you don't have account in 'Sendinblue', you may create your own in this link.

In this part of the experiment, we will be using Sendinblue to send emails. If you don't have an account at 'Sendinblue', you may create one at this link.

Setting up your Sendinblue Account

Once you are done creating your own account, you should see this page: image


Now, click Transactional and then Settings in the left navigation bar. You should be able to see this page: image


Once you are there, click Configuration and Get Your SMTP Key. You should be on this page. Sendinblue


Now, click Generate a new SMTP key. A modal should appear, and you may enter any key you want. In my case, I use lab_exp. Then click the Generate button.

After you click the Generate button, the SMTP key of Sendinblue should appear; copy that and don't lose it.

Setting up an SMTP Server in PocketBase

To set up SMTP in PocketBase, go to the admin UI home page. Then click the setting icon and click on Mail Settings. You should be able to see this page:

image


Now, toggle Use SMTP mail server (recommended). A user interface for configuring your SMTP mail server would appear. Remember the SMTP key you copied earlier? You'll be pasting that in the password field. image


For the username field, go back to Sendinblue and close the modal for the SMTP key. Copy the login: Sendinblue(1)


For the SMTP Server Host field, paste smtp-relay.sendinblue.com, which can be seen back on the Sendinblue page. Then you may now click Save changes.

Send test email will appear, and when you click that, you can test if the configuration you made is working. Sendinblue(2)

Error in Setting up PocketBase Mail Settings

This is an optional part of the documentation in case you encountered an error while setting up the mail settings of PocketBase.

During the experiment at the lab, I encounter this error: pb-error

To resolve this issue, delete the following files:

  • CHANGELOG.md
  • LICENSE.md
  • pocketbase.exe
  • pb_data

Since you deleted the pb_data. You have to repeat the configuration of PocketBase mail settings and create an admin account for the Admin UI. You may read the Dangers of Deleting pb_data included in this documentation.

PocketBase Collection Types

In PocketBase, there are two types of collections:

  • Base Collection - is the default collection type, and you can use it for any type of data.
  • Auth Collection - contains extra fields to manage users, like username, email, and verified. An example of an Auth collection is the PocketBase default user collections.

This was discussed because later we will be creating a simple way of authenticating users.

Source:https://www.makeuseof.com/pocketbase-what-and-how/#:~:text=Creating%20a%20Collection%20in%20PocketBase&text=A%20collection%20can%20either%20be,username%2C%20email%2C%20and%20verified.

Getting the Records

In getting the records into PocketBase, here is an example from the official documentation:

importPocketBasefrom'pocketbase';constpb=newPocketBase('http://127.0.0.1:8090');
...
constrecord1=awaitpb.collection('posts').getOne('RECORD_ID',{expand: 'relField1,relField2.subRelField',});

This is how we implement it in hooks/UseVerified.js:

constuser_id=PB.authStore.model.id;constuser_data=awaitPB.collection("users").getOne(user_id);setIsVerified(user_data.verified);

Sending the Email Verification

To start off, we need the following:

  • First, a button that will send email verification
  • Then, a function will handle the click event for sending email verification.
  • Lastly, an indicator of whether the user is verified or not

Earlier, UseVerified.js was mentioned; add the following function to that file:

asyncfunctionrequestVerification(){constemail=PB.authStore.model.email;constresponse=awaitPB.collection("users").requestVerification(email);if(response){alert("Verification email sent! Check your inbox.");}}

Now, we're done with the function that will handle the sending of emails. In Auth.js, import the UseVerified custom hook:

importUseVerifiedfrom"hooks/UseVerified";
...
const{ is_verified, requestVerification }=UseVerified();

Now, create the indicator and the button in Auth.js:

<p>Verified: {is_verified.toString()}</p>{!is_verified&&<buttononClick={requestVerification}>Send Verification</button>}

Your UseVerified.js should look like the following:

import{useState,useEffect}from"react";importPBfrom"lib/pocketbase";functionUseVerified(){const[is_verified,setIsVerified]=useState(false);useEffect(()=>{asyncfunctioncheckVerified(){constuser_id=PB.authStore.model.id;constuser_data=awaitPB.collection("users").getOne(user_id);console.log("Fetch data: ",user_data);setIsVerified(user_data.verified);}constis_logged_in=PB.authStore.isValid;/* Invoke checkVerified() function if the user is logged in */if(is_logged_in){checkVerified();}},[]);asyncfunctionrequestVerification(){constemail=PB.authStore.model.email;constresponse=awaitPB.collection("users").requestVerification(email);if(response){alert("Verification email sent! Check your inbox.");}}return{ is_verified, requestVerification };}exportdefaultUseVerified;

In testing it, you should use your email account, and you should be able to receive this email once you test it: Verified

Click the Verify button in the email, return to your React app, and refresh it. Verified should have a value of true now.

Back to top

Automatic Re-fetching with the useQuery Hook

In this chapter of the documentation, we will be making changes to the app from the previous chapter and enabling it to reflect the changes that are happening in the database (PocketBase).

In order to do this, we will be using useQuery in React, and you may visit the official documentation by clicking this.

There will be a huge changes in UseVerified.js, first import the useQuery:

There will be huge changes in UseVerified.js. First, import useQuery.

import{useQuery}from"react-query";

Then UseVerified() will have a different return value:

returnuseQuery({queryFn: checkVerified,queryKey: ["check-verified",user_id]});

This is the last chapter in this documentation; you can click this to see the whole change in UseVerified.js. There were minor changes in Auth.js as well; you can click [this](https://github.com/MadrinanComLab/Exp-PocketBase/blob/master/pb_app/src/components/Auth.js to see the changes.

Explaining the Attributes of the useQuery Option

Keys in the option of useQuery was case sensitive. For example, you cannot make queryFn into queryFunction or other keys that you may wish to use.

The object that is used as an argument is called options, and each of its attributes has a different purpose:

  • queryFn - The function you will assign here will be responsible for fetching the data you need.

Note: Since checkVerified was used to be the value of queryFunction, the return value of it will change:

returnuser_data.verified
  • queryKey - This will be used for data caching, and the value of "check-verified" will be the key, and user_id is the second argument, which in this case will be used to query the user by its record id.

Note: The value of user_id will come from the code snippet below. Remember, this will have a value when you log in a user, then it becomes undefined when no user is logged in, and that is why optional chaining was implemented.

...
exportdefaultfunctionUseVerified(){constuser_id=PB.authStore.model?.id;
...
}

Back to top


Did You Like This Experiment?

Stay tuned for upcoming projects and experiments by following me on the following accounts:

About

This repository is an experiment that aims to learn about PocketBase and provide helpful notes to the lab and other developers who want to use PocketBase as a beginner.

Topics

Resources

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - MadrinanComLab/Exp-PocketBase: This repository is an experiment that aims to learn about PocketBase and provide helpful notes to the lab and other developers who want to use PocketBase as a beginner. · GitHub
Skip to content

Latest commit

History

74 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Welcome to PocketBase Experiment!

PocketBase-Experiment

Reference for this experiment:https://www.youtube.com/watch?v=rediFff54JA&t=75s
This experiment was initiated on April 9, 2023.

Table of Contents

Introduction to PocketBase

PocketBase is similar to Firebase (a Google service) and Supabase (an open-source Firebase alternative), which handle backend functions like file upload, saving data to a database, etc.

I know that my introduction to PocketBase was too short to describe it, so here is the link to a video of Fireship where he fully introduces PocketBase.

You can download PocketBase using this link.

Back to top

How to Run PocketBase?

Once you've downloaded PocketBase and it was now in Downloads, do the following:
1. Extract the ZIP file that you downloaded.
2. Rename it PocketBase or however you like, but keep it short.
3. Go to that directory, e.g., Downloads/PocketBase.
4. Now enter this command:

./pocketbase serve

If the command above didn't work for you, try this:

pocketbase serve

Note: The pocketbase you see in the command is not the folder name, it was the keyword for the command.

5. After running the previous command you will see something like this:

2023/04/0922:11:46Serverstartedat http://127.0.0.1:8090REST API: http://127.0.0.1:8090/api/AdminUI: http://127.0.0.1:8090/_/

Back to top

Explaining the Links

Based on the official documentation, the meaning of the links are the following:
http://127.0.0.1:8090 - if pb_public directory exists, serves the static content from it (html, css, images, etc.)
http://127.0.0.1:8090/_/ - Admin dashboard UI
http://127.0.0.1:8090/api/ - REST API

Back to top

Folders in a Pre-Built Pocket Base

The prebuilt PocketBase executable will automatically create and manage 2 new directories alongside the executable:
pb_data - stores your application data, uploaded files, etc. (usually should be added in .gitignore).
pb_migrations - contains JS migration files with your collection changes (can be safely committed in your repository).

Back to top

User-Defined Folder

You have to add the following folder:
pb_public - This is the folder that will be hosted by PocketBase. Here is an example:
1. After creating the pb_public folder, create a file inside this folder named index.html and copy and paste the following code:

<!DOCTYPE html><htmllang="en"><head><metacharset="UTF-8"><metahttp-equiv="X-UA-Compatible" content="IE=edge"><metaname="viewport" content="width=device-width, initial-scale=1.0"><title>Testing123</title></head><body><h1>Welcome to PocketBase Experiment by MadriñanComLab!</h1></body></html>

2. Then go to this URL:

http://127.0.0.1:8090/

Back to top

Adding a React Application to a PocketBase App:

To add the React app, just do the following command:

npm create react-app <app_name>

Example:

npm create react-app pb_app

Back to top

Regaining Access to the PocketBase Admin UI

Just in case you have forgotten your password. What you can do is:
1. Stop the server of PocketBase on your computer (if it is running).
2. Delete the pb_data
3. Run your PocketBase
4. Go to http://127.0.0.1:8090/_/
5. Then you can now set your new email and password.

Back to top

Dangers of Deleting pb_data

Deleting this folder was helpful when you forgot your password for the admin UI, but deleting it will also affect the configuration you set in your PocketBase application.

Later on, configuring the mail settings of PocketBase will be discussed in this documentation.

Additional Note: By deleting the pb_data, you are deleting the data in your database. DO NOT delete pb_migration because this contains the structure of your database. You may delete it unless you intend to delete the structure of the database. But to just regain access to the admin side of your database, just delete the pb_data.

Back to top

Creating a New Collection

The admin UI was straightforward, but in case you are hesitant, here is a brief introduction: Steps-1-to-31. Start with creating your collection. After you click that, a modal will appear on the right.
2. Name your collection however you want. Think of a collection as a table, and you can see in the suggestion that it follows the naming convention of SQL (where tables are named plural).
3. After naming your first collection, you can then create the first field in your collection.

Steps-4-to-54. After clicking the New field button, options for data type would appear.
5. Name your first field here. Think of a field as a column in your database.
Steps-66. If you click the cog icon inline with your field, an option for min and max length would appear as well as the regex.
Steps-7PB-Index-Modal7. If you click "New index," a modal will appear for you to setup your indexes.

Unfamiliar with indexes? You can read about it here

8. You can now save your first collection. Ow, wait, you aren't able to save it? Well, that was because there is already a "users" collection that was initially included in your first PocketBase application.

I apologize about it; this README was created while first exploring PocketBase.

Back to top

React and PocketBase SDK Setup

In the tutorial, the following files were added to the root folder of pb_app:
.env.local
jsconfig.json - In the code here, what it does is make the src folder the root directory for importing and avoid the example imports below.:

importxfrom"../../../folder/file";

Delete the following in pb_app/src:
App.css
App.test.js
index.css
logo.svg
reportWebVitals.js
setupTest.js
Remove the following in index.js:

importreportWebVitalsfrom'./reportWebVitals';import'./index.css';
...
// If you want to start measuring performance in your app, pass a function// to log results (for example: reportWebVitals(console.log))// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitalsreportWebVitals();

Remove the following in App.js:

importlogofrom'./logo.svg';import'./App.css';
...
// This is the chuck of code in return statement<divclassName="App"><headerclassName="App-header"><imgsrc={logo}className="App-logo"alt="logo"/><p>
Edit <code>src/App.js</code> and save to reload.
</p><aclassName="App-link"href="https://reactjs.org"target="_blank"rel="noopener noreferrer">
Learn React
</a></header></div>

You can now add the JavaScript PocketBase SDK by running the following command in your terminal:

npm install pocketbase --save

Note: Make sure you are in the pb_app directory, where the React app is.

Then create the /lib/pocketbase.js file inside the src folder and add the following code snippet:

importPocketBasefrom"pocketbase";constpb=newPocketBase(process.env.REACT_APP_PB_URL);exportdefaultpb;

Note: The value of REACT_APP_PB_URL was http://127.0.0.1:8090, and it was in the .env.local

Now, to test if everything is working, run the following command:

npm start

Back to top

Authentication (with react-hook-form)

PocketBase provides documentation about their authentication; you can read it here if you want to read more about it.

Examples

The following will be a set of different examples of PocketBase authentication.

#1

First, create Auth.js in src. For now, the Auth component will contain the following:

importPBfrom"lib/pocketbase";functionAuth(){return(<>{/* For a simple example of pocketbase authentication */}<h1>Logged In: {PB.authStore.isValid.toString()}</h1></>);}exportdefaultAuth;

Import the Auth component to App.js:

importAuthfrom"Auth";functionApp(){return(<><Auth/></>);}exportdefaultApp;

Your browser should display something like this: ss-3

The first example makes sense that it displays false because a user hasn't logged in yet, right?

#2

Now, let's level up the previous example. Still in Auth.js, do the following:

importPBfrom"lib/pocketbase";import{useState}from"react";import{useForm}from"react-hook-form";functionAuth(){const{ register, handleSubmit }=useForm();const[is_loading,setLoading]=useState(false);asyncfunctionlogin(data){/* This function was a custom function of handleSubmit() */setLoading(true);try{constauth_data=awaitPB.collection("users").authWithPassword(data.email,data.password);}catch(error){console.log(error);}setLoading(false);}return(<><h1>Logged In: {PB.authStore.isValid.toString()}</h1>{is_loading&&<p>Loading...</p>}<formonSubmit={handleSubmit(login)}><inputtype="text"placeholder="email"{...register("email")}/><inputtype="password"placeholder="password"{...register("password")}/><buttontype="submit"disabled={is_loading}>Login</button></form></>);}exportdefaultAuth;

Before we proceed with creating the first user, let's breakdown the example code above because there was a huge change between examples #1 and #2.

  • First is about the React-hook-form, which will help us handle the sample login:
const{ register, handleSubmit }=useForm();

To install react-hook-form in your project, just run the following command:

npm install react-hook-form

Note: Make sure you are in pb_app folder before you install this npm library.

  • State object that will be used for a simple loading indicator.
const[is_loading,setLoading]=useState(false);

  • Then, on the actual code for validation, there was a comment on the code to help break it down:
asyncfunctionlogin(data){/* This function was a custom function of handleSubmit() *//* Set is_loading to true to indicated the request in on process */setLoading(true);/* Try catch statement was used to handle the possible error that may occur. */try{constauth_data=awaitPB/* The 'collection' is the collection of pocketbase */.collection("users")/* 'authWithPassword' will handle the authentication */.authWithPassword(data.email,data.password);}catch(error){console.log(error);}setLoading(false);}

Now, let's create our first user. You can use whatever email and password you want: sample-usersample-user(1)

After this, you can try it to see if it works.

#3

Users can now log in, and for the last example, we will enable users to log out. Do this in Auth.js.:

importPBfrom"lib/pocketbase";import{useState,useEffect}from"react";import{useForm}from"react-hook-form";functionAuth(){const{ register, handleSubmit }=useForm();const[is_loading,setLoading]=useState(false);constis_logged_in=PB.authStore.isValid;/* Later, this will be use to re-render the Auth component */const[dummy,setDummy]=useState(0);asyncfunctionlogin(data){/* This function was a custom function of handleSubmit() */setLoading(true);try{constauth_data=awaitPB.collection("users").authWithPassword(data.email,data.password);}catch(error){console.log(error);}setLoading(false);}functionlogout(){/* logout() function was not an asynchronous function because it doesn't do an API call. Instead, it simply clear the cookie */PB.authStore.clear();setDummy(Math.random());}/* Just another way of conditional rendering in React */if(is_logged_in){return(<>{/* If a user has logged in, display the email address */}<h1>Logged In: {is_logged_in&&PB.authStore.model.email}</h1><buttononClick={logout}>Log Out</button></>);}return(<><h1>Welcome to Login Page!</h1>{is_loading&&<p>Loading...</p>}<formonSubmit={handleSubmit(login)}><inputtype="text"placeholder="email"{...register("email")}/><inputtype="password"placeholder="password"{...register("password")}/><buttontype="submit"disabled={is_loading}>Login</button></form></>);}exportdefaultAuth;

Back to top

Auth Hooks (with react-query)

In this part of the documentation, we will be destructuring the example code from previous chapter.

Destructuring Logout Function

First, we'll destructure the function for logging out. Create a hooks folder in src and create UseLogout.js, which will contain the following code:

importPBfrom"lib/pocketbase";import{useState}from"react";functionUseLogout(){/* Later, this will be use to re-render the Auth component */const[dummy,setDummy]=useState(0);functionlogout(){/* logout() function was not an asynchronous function because it doesn't do an API call. Instead, it simply clear the cookie */PB.authStore.clear();setDummy(Math.random());}returnlogout;}exportdefaultUseLogout;

Note: The code above was an example of React custom hooks.


The Auth.js will look like this:

/* The custom hook was imported to Auth.js */importUseLogoutfrom"hooks/UseLogout";importPBfrom"lib/pocketbase";import{useState,useEffect}from"react";import{useForm}from"react-hook-form";functionAuth(){/* logout() function was declared like this */constlogout=UseLogout();const{ register, handleSubmit }=useForm();const[is_loading,setLoading]=useState(false);constis_logged_in=PB.authStore.isValid;asyncfunctionlogin(data){/* This function was a custom function of handleSubmit() */setLoading(true);try{constauth_data=awaitPB.collection("users").authWithPassword(data.email,data.password);}catch(error){console.log(error);}setLoading(false);}if(is_logged_in){return(<>{/* If a user has logged in, display the email address */}<h1>Logged In: {is_logged_in&&PB.authStore.model.email}</h1><buttononClick={logout}>Log Out</button></>);}return(<><h1>Welcome to Login Page!</h1>{is_loading&&<p>Loading...</p>}<formonSubmit={handleSubmit(login)}><inputtype="text"placeholder="email"{...register("email")}/><inputtype="password"placeholder="password"{...register("password")}/><buttontype="submit"disabled={is_loading}>Login</button></form></>);}exportdefaultAuth;

If you try this, it will work well, but the same problem as in the previous chapter will remain: the credentials you've inputted will remain.

To resolve this, add reset in the destructuring of useForm():

const{ register, handleSubmit, reset }=useForm();

Then, add this reset() function to the login function:

asyncfunctionlogin(data){/* This function was a custom function of handleSubmit() */setLoading(true);try{constauth_data=awaitPB.collection("users").authWithPassword(data.email,data.password);}catch(error){console.log(error);}setLoading(false);/* reset() was added here */reset();}

Destructuring Login Function

Now that we've destructured the logout function using a custom hook, we'll take a few steps to destructure the login as well.

Create UseLogin.js and it will contain the following:

importPBfrom"lib/pocketbase";import{useState}from"react";functionUseLogin(){const[is_loading,setLoading]=useState(false);asyncfunctionlogin({ email, password }){/* This function was a custom function of handleSubmit() */setLoading(true);try{constauth_data=awaitPB.collection("users").authWithPassword(email,password);}catch(error){console.log(error);}setLoading(false);}return{ login, is_loading };}exportdefaultUseLogin;

Since we removed the login function, Auth.js will look like this:

importUseLoginfrom"hooks/UseLogin";importUseLogoutfrom"hooks/UseLogout";importPBfrom"lib/pocketbase";import{useForm}from"react-hook-form";functionAuth(){constlogout=UseLogout();const{ login, is_loading }=UseLogin();const{ register, handleSubmit, reset }=useForm();constis_logged_in=PB.authStore.isValid;asyncfunctiononSubmit(data){login(data);reset();}if(is_logged_in){return(<>{/* If a user has logged in, display the email address */}<h1>Logged In: {is_logged_in&&PB.authStore.model.email}</h1><buttononClick={logout}>Log Out</button></>);}return(<><h1>Welcome to Login Page!</h1>{is_loading&&<p>Loading...</p>}<formonSubmit={handleSubmit(onSubmit)}><inputtype="text"placeholder="email"{...register("email")}/><inputtype="password"placeholder="password"{...register("password")}/><buttontype="submit"disabled={is_loading}>Login</button></form></>);}exportdefaultAuth;

Destructuring API call to PocketBase

In this part of the documentation, we will be using the react-query, and to install this library, run this command:

npm install react-query

Before we proceed to destructuring, we have to do the following on App.js to be able to implement the react-query and do the destructuring:

importAuthfrom"Auth";/* Import the necessary functions */import{QueryClientProvider,QueryClient}from"react-query";/* Initialize the QueryClient */constqueryClient=newQueryClient();functionApp(){return({/* Wrap the Auth component with QueryClientProvider and use queryClient as client */}<QueryClientProviderclient={ queryClient }><Auth/></QueryClientProvider>);}exportdefaultApp;

UseLogin.js will look like this after the destructuring:

importPBfrom"lib/pocketbase";/* useMutation was imported */import{useMutation}from"react-query"/* useState for is_loading was removed because react-query has built-in and it was isLoading*/functionUseLogin(){/* Because login() function was used as argument to useMutation, we no longer need to wrap it with try catch */asyncfunctionlogin({ email, password }){/* This function was a custom function of handleSubmit() */constauth_data=awaitPB.collection("users").authWithPassword(email,password);}/* login() was passed in useMutation as argument */returnuseMutation(login)}exportdefaultUseLogin;

And because of these changes, Auth.js will look like this:

importUseLoginfrom"hooks/UseLogin";importUseLogoutfrom"hooks/UseLogout";importPBfrom"lib/pocketbase";import{useForm}from"react-hook-form";functionAuth(){constlogout=UseLogout();const{/* "mutate" will be used to invoke the login() function */
mutate,/* "is_loading" is now isLoading() which is available to useMutation() by default */
isLoading,/* isError was also available to useMutation() by default */
isError }=UseLogin();const{ register, handleSubmit, reset }=useForm();constis_logged_in=PB.authStore.isValid;asyncfunctiononSubmit(data){mutate(data);reset();}if(is_logged_in){return(<>{/* If a user has logged in, display the email address */}<h1>Logged In: {is_logged_in&&PB.authStore.model.email}</h1><buttononClick={logout}>Log Out</button></>);}return(<><h1>Welcome to Login Page!</h1>{isLoading&&<p>Loading...</p>}{isError&&<p>Invalid email or password</p>}<formonSubmit={handleSubmit(onSubmit)}><inputtype="text"placeholder="email"{...register("email")}/><inputtype="password"placeholder="password"{...register("password")}/><buttontype="submit"disabled={isLoading}>Login</button></form></>);}exportdefaultAuth;

If you want to read more about react-query, here is the official documentation.

Back to top

Sending Verification Emails with Brevo (formerly Sendinblue) SMTP

In this part of the experiment, we will be using Sendinblue to send emails. If you don't have account in 'Sendinblue', you may create your own in this link.

In this part of the experiment, we will be using Sendinblue to send emails. If you don't have an account at 'Sendinblue', you may create one at this link.

Setting up your Sendinblue Account

Once you are done creating your own account, you should see this page: image


Now, click Transactional and then Settings in the left navigation bar. You should be able to see this page: image


Once you are there, click Configuration and Get Your SMTP Key. You should be on this page. Sendinblue


Now, click Generate a new SMTP key. A modal should appear, and you may enter any key you want. In my case, I use lab_exp. Then click the Generate button.

After you click the Generate button, the SMTP key of Sendinblue should appear; copy that and don't lose it.

Setting up an SMTP Server in PocketBase

To set up SMTP in PocketBase, go to the admin UI home page. Then click the setting icon and click on Mail Settings. You should be able to see this page:

image


Now, toggle Use SMTP mail server (recommended). A user interface for configuring your SMTP mail server would appear. Remember the SMTP key you copied earlier? You'll be pasting that in the password field. image


For the username field, go back to Sendinblue and close the modal for the SMTP key. Copy the login: Sendinblue(1)


For the SMTP Server Host field, paste smtp-relay.sendinblue.com, which can be seen back on the Sendinblue page. Then you may now click Save changes.

Send test email will appear, and when you click that, you can test if the configuration you made is working. Sendinblue(2)

Error in Setting up PocketBase Mail Settings

This is an optional part of the documentation in case you encountered an error while setting up the mail settings of PocketBase.

During the experiment at the lab, I encounter this error: pb-error

To resolve this issue, delete the following files:

  • CHANGELOG.md
  • LICENSE.md
  • pocketbase.exe
  • pb_data

Since you deleted the pb_data. You have to repeat the configuration of PocketBase mail settings and create an admin account for the Admin UI. You may read the Dangers of Deleting pb_data included in this documentation.

PocketBase Collection Types

In PocketBase, there are two types of collections:

  • Base Collection - is the default collection type, and you can use it for any type of data.
  • Auth Collection - contains extra fields to manage users, like username, email, and verified. An example of an Auth collection is the PocketBase default user collections.

This was discussed because later we will be creating a simple way of authenticating users.

Source:https://www.makeuseof.com/pocketbase-what-and-how/#:~:text=Creating%20a%20Collection%20in%20PocketBase&text=A%20collection%20can%20either%20be,username%2C%20email%2C%20and%20verified.

Getting the Records

In getting the records into PocketBase, here is an example from the official documentation:

importPocketBasefrom'pocketbase';constpb=newPocketBase('http://127.0.0.1:8090');
...
constrecord1=awaitpb.collection('posts').getOne('RECORD_ID',{expand: 'relField1,relField2.subRelField',});

This is how we implement it in hooks/UseVerified.js:

constuser_id=PB.authStore.model.id;constuser_data=awaitPB.collection("users").getOne(user_id);setIsVerified(user_data.verified);

Sending the Email Verification

To start off, we need the following:

  • First, a button that will send email verification
  • Then, a function will handle the click event for sending email verification.
  • Lastly, an indicator of whether the user is verified or not

Earlier, UseVerified.js was mentioned; add the following function to that file:

asyncfunctionrequestVerification(){constemail=PB.authStore.model.email;constresponse=awaitPB.collection("users").requestVerification(email);if(response){alert("Verification email sent! Check your inbox.");}}

Now, we're done with the function that will handle the sending of emails. In Auth.js, import the UseVerified custom hook:

importUseVerifiedfrom"hooks/UseVerified";
...
const{ is_verified, requestVerification }=UseVerified();

Now, create the indicator and the button in Auth.js:

<p>Verified: {is_verified.toString()}</p>{!is_verified&&<buttononClick={requestVerification}>Send Verification</button>}

Your UseVerified.js should look like the following:

import{useState,useEffect}from"react";importPBfrom"lib/pocketbase";functionUseVerified(){const[is_verified,setIsVerified]=useState(false);useEffect(()=>{asyncfunctioncheckVerified(){constuser_id=PB.authStore.model.id;constuser_data=awaitPB.collection("users").getOne(user_id);console.log("Fetch data: ",user_data);setIsVerified(user_data.verified);}constis_logged_in=PB.authStore.isValid;/* Invoke checkVerified() function if the user is logged in */if(is_logged_in){checkVerified();}},[]);asyncfunctionrequestVerification(){constemail=PB.authStore.model.email;constresponse=awaitPB.collection("users").requestVerification(email);if(response){alert("Verification email sent! Check your inbox.");}}return{ is_verified, requestVerification };}exportdefaultUseVerified;

In testing it, you should use your email account, and you should be able to receive this email once you test it: Verified

Click the Verify button in the email, return to your React app, and refresh it. Verified should have a value of true now.

Back to top

Automatic Re-fetching with the useQuery Hook

In this chapter of the documentation, we will be making changes to the app from the previous chapter and enabling it to reflect the changes that are happening in the database (PocketBase).

In order to do this, we will be using useQuery in React, and you may visit the official documentation by clicking this.

There will be a huge changes in UseVerified.js, first import the useQuery:

There will be huge changes in UseVerified.js. First, import useQuery.

import{useQuery}from"react-query";

Then UseVerified() will have a different return value:

returnuseQuery({queryFn: checkVerified,queryKey: ["check-verified",user_id]});

This is the last chapter in this documentation; you can click this to see the whole change in UseVerified.js. There were minor changes in Auth.js as well; you can click [this](https://github.com/MadrinanComLab/Exp-PocketBase/blob/master/pb_app/src/components/Auth.js to see the changes.

Explaining the Attributes of the useQuery Option

Keys in the option of useQuery was case sensitive. For example, you cannot make queryFn into queryFunction or other keys that you may wish to use.

The object that is used as an argument is called options, and each of its attributes has a different purpose:

  • queryFn - The function you will assign here will be responsible for fetching the data you need.

Note: Since checkVerified was used to be the value of queryFunction, the return value of it will change:

returnuser_data.verified
  • queryKey - This will be used for data caching, and the value of "check-verified" will be the key, and user_id is the second argument, which in this case will be used to query the user by its record id.

Note: The value of user_id will come from the code snippet below. Remember, this will have a value when you log in a user, then it becomes undefined when no user is logged in, and that is why optional chaining was implemented.

...
exportdefaultfunctionUseVerified(){constuser_id=PB.authStore.model?.id;
...
}

Back to top


Did You Like This Experiment?

Stay tuned for upcoming projects and experiments by following me on the following accounts:

About

This repository is an experiment that aims to learn about PocketBase and provide helpful notes to the lab and other developers who want to use PocketBase as a beginner.

Topics

Resources

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - MadrinanComLab/Exp-PocketBase: This repository is an experiment that aims to learn about PocketBase and provide helpful notes to the lab and other developers who want to use PocketBase as a beginner. · GitHub
Skip to content

Latest commit

History

74 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Welcome to PocketBase Experiment!

PocketBase-Experiment

Reference for this experiment:https://www.youtube.com/watch?v=rediFff54JA&t=75s
This experiment was initiated on April 9, 2023.

Table of Contents

Introduction to PocketBase

PocketBase is similar to Firebase (a Google service) and Supabase (an open-source Firebase alternative), which handle backend functions like file upload, saving data to a database, etc.

I know that my introduction to PocketBase was too short to describe it, so here is the link to a video of Fireship where he fully introduces PocketBase.

You can download PocketBase using this link.

Back to top

How to Run PocketBase?

Once you've downloaded PocketBase and it was now in Downloads, do the following:
1. Extract the ZIP file that you downloaded.
2. Rename it PocketBase or however you like, but keep it short.
3. Go to that directory, e.g., Downloads/PocketBase.
4. Now enter this command:

./pocketbase serve

If the command above didn't work for you, try this:

pocketbase serve

Note: The pocketbase you see in the command is not the folder name, it was the keyword for the command.

5. After running the previous command you will see something like this:

2023/04/0922:11:46Serverstartedat http://127.0.0.1:8090REST API: http://127.0.0.1:8090/api/AdminUI: http://127.0.0.1:8090/_/

Back to top

Explaining the Links

Based on the official documentation, the meaning of the links are the following:
http://127.0.0.1:8090 - if pb_public directory exists, serves the static content from it (html, css, images, etc.)
http://127.0.0.1:8090/_/ - Admin dashboard UI
http://127.0.0.1:8090/api/ - REST API

Back to top

Folders in a Pre-Built Pocket Base

The prebuilt PocketBase executable will automatically create and manage 2 new directories alongside the executable:
pb_data - stores your application data, uploaded files, etc. (usually should be added in .gitignore).
pb_migrations - contains JS migration files with your collection changes (can be safely committed in your repository).

Back to top

User-Defined Folder

You have to add the following folder:
pb_public - This is the folder that will be hosted by PocketBase. Here is an example:
1. After creating the pb_public folder, create a file inside this folder named index.html and copy and paste the following code:

<!DOCTYPE html><htmllang="en"><head><metacharset="UTF-8"><metahttp-equiv="X-UA-Compatible" content="IE=edge"><metaname="viewport" content="width=device-width, initial-scale=1.0"><title>Testing123</title></head><body><h1>Welcome to PocketBase Experiment by MadriñanComLab!</h1></body></html>

2. Then go to this URL:

http://127.0.0.1:8090/

Back to top

Adding a React Application to a PocketBase App:

To add the React app, just do the following command:

npm create react-app <app_name>

Example:

npm create react-app pb_app

Back to top

Regaining Access to the PocketBase Admin UI

Just in case you have forgotten your password. What you can do is:
1. Stop the server of PocketBase on your computer (if it is running).
2. Delete the pb_data
3. Run your PocketBase
4. Go to http://127.0.0.1:8090/_/
5. Then you can now set your new email and password.

Back to top

Dangers of Deleting pb_data

Deleting this folder was helpful when you forgot your password for the admin UI, but deleting it will also affect the configuration you set in your PocketBase application.

Later on, configuring the mail settings of PocketBase will be discussed in this documentation.

Additional Note: By deleting the pb_data, you are deleting the data in your database. DO NOT delete pb_migration because this contains the structure of your database. You may delete it unless you intend to delete the structure of the database. But to just regain access to the admin side of your database, just delete the pb_data.

Back to top

Creating a New Collection

The admin UI was straightforward, but in case you are hesitant, here is a brief introduction: Steps-1-to-31. Start with creating your collection. After you click that, a modal will appear on the right.
2. Name your collection however you want. Think of a collection as a table, and you can see in the suggestion that it follows the naming convention of SQL (where tables are named plural).
3. After naming your first collection, you can then create the first field in your collection.

Steps-4-to-54. After clicking the New field button, options for data type would appear.
5. Name your first field here. Think of a field as a column in your database.
Steps-66. If you click the cog icon inline with your field, an option for min and max length would appear as well as the regex.
Steps-7PB-Index-Modal7. If you click "New index," a modal will appear for you to setup your indexes.

Unfamiliar with indexes? You can read about it here

8. You can now save your first collection. Ow, wait, you aren't able to save it? Well, that was because there is already a "users" collection that was initially included in your first PocketBase application.

I apologize about it; this README was created while first exploring PocketBase.

Back to top

React and PocketBase SDK Setup

In the tutorial, the following files were added to the root folder of pb_app:
.env.local
jsconfig.json - In the code here, what it does is make the src folder the root directory for importing and avoid the example imports below.:

importxfrom"../../../folder/file";

Delete the following in pb_app/src:
App.css
App.test.js
index.css
logo.svg
reportWebVitals.js
setupTest.js
Remove the following in index.js:

importreportWebVitalsfrom'./reportWebVitals';import'./index.css';
...
// If you want to start measuring performance in your app, pass a function// to log results (for example: reportWebVitals(console.log))// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitalsreportWebVitals();

Remove the following in App.js:

importlogofrom'./logo.svg';import'./App.css';
...
// This is the chuck of code in return statement<divclassName="App"><headerclassName="App-header"><imgsrc={logo}className="App-logo"alt="logo"/><p>
Edit <code>src/App.js</code> and save to reload.
</p><aclassName="App-link"href="https://reactjs.org"target="_blank"rel="noopener noreferrer">
Learn React
</a></header></div>

You can now add the JavaScript PocketBase SDK by running the following command in your terminal:

npm install pocketbase --save

Note: Make sure you are in the pb_app directory, where the React app is.

Then create the /lib/pocketbase.js file inside the src folder and add the following code snippet:

importPocketBasefrom"pocketbase";constpb=newPocketBase(process.env.REACT_APP_PB_URL);exportdefaultpb;

Note: The value of REACT_APP_PB_URL was http://127.0.0.1:8090, and it was in the .env.local

Now, to test if everything is working, run the following command:

npm start

Back to top

Authentication (with react-hook-form)

PocketBase provides documentation about their authentication; you can read it here if you want to read more about it.

Examples

The following will be a set of different examples of PocketBase authentication.

#1

First, create Auth.js in src. For now, the Auth component will contain the following:

importPBfrom"lib/pocketbase";functionAuth(){return(<>{/* For a simple example of pocketbase authentication */}<h1>Logged In: {PB.authStore.isValid.toString()}</h1></>);}exportdefaultAuth;

Import the Auth component to App.js:

importAuthfrom"Auth";functionApp(){return(<><Auth/></>);}exportdefaultApp;

Your browser should display something like this: ss-3

The first example makes sense that it displays false because a user hasn't logged in yet, right?

#2

Now, let's level up the previous example. Still in Auth.js, do the following:

importPBfrom"lib/pocketbase";import{useState}from"react";import{useForm}from"react-hook-form";functionAuth(){const{ register, handleSubmit }=useForm();const[is_loading,setLoading]=useState(false);asyncfunctionlogin(data){/* This function was a custom function of handleSubmit() */setLoading(true);try{constauth_data=awaitPB.collection("users").authWithPassword(data.email,data.password);}catch(error){console.log(error);}setLoading(false);}return(<><h1>Logged In: {PB.authStore.isValid.toString()}</h1>{is_loading&&<p>Loading...</p>}<formonSubmit={handleSubmit(login)}><inputtype="text"placeholder="email"{...register("email")}/><inputtype="password"placeholder="password"{...register("password")}/><buttontype="submit"disabled={is_loading}>Login</button></form></>);}exportdefaultAuth;

Before we proceed with creating the first user, let's breakdown the example code above because there was a huge change between examples #1 and #2.

  • First is about the React-hook-form, which will help us handle the sample login:
const{ register, handleSubmit }=useForm();

To install react-hook-form in your project, just run the following command:

npm install react-hook-form

Note: Make sure you are in pb_app folder before you install this npm library.

  • State object that will be used for a simple loading indicator.
const[is_loading,setLoading]=useState(false);

  • Then, on the actual code for validation, there was a comment on the code to help break it down:
asyncfunctionlogin(data){/* This function was a custom function of handleSubmit() *//* Set is_loading to true to indicated the request in on process */setLoading(true);/* Try catch statement was used to handle the possible error that may occur. */try{constauth_data=awaitPB/* The 'collection' is the collection of pocketbase */.collection("users")/* 'authWithPassword' will handle the authentication */.authWithPassword(data.email,data.password);}catch(error){console.log(error);}setLoading(false);}

Now, let's create our first user. You can use whatever email and password you want: sample-usersample-user(1)

After this, you can try it to see if it works.

#3

Users can now log in, and for the last example, we will enable users to log out. Do this in Auth.js.:

importPBfrom"lib/pocketbase";import{useState,useEffect}from"react";import{useForm}from"react-hook-form";functionAuth(){const{ register, handleSubmit }=useForm();const[is_loading,setLoading]=useState(false);constis_logged_in=PB.authStore.isValid;/* Later, this will be use to re-render the Auth component */const[dummy,setDummy]=useState(0);asyncfunctionlogin(data){/* This function was a custom function of handleSubmit() */setLoading(true);try{constauth_data=awaitPB.collection("users").authWithPassword(data.email,data.password);}catch(error){console.log(error);}setLoading(false);}functionlogout(){/* logout() function was not an asynchronous function because it doesn't do an API call. Instead, it simply clear the cookie */PB.authStore.clear();setDummy(Math.random());}/* Just another way of conditional rendering in React */if(is_logged_in){return(<>{/* If a user has logged in, display the email address */}<h1>Logged In: {is_logged_in&&PB.authStore.model.email}</h1><buttononClick={logout}>Log Out</button></>);}return(<><h1>Welcome to Login Page!</h1>{is_loading&&<p>Loading...</p>}<formonSubmit={handleSubmit(login)}><inputtype="text"placeholder="email"{...register("email")}/><inputtype="password"placeholder="password"{...register("password")}/><buttontype="submit"disabled={is_loading}>Login</button></form></>);}exportdefaultAuth;

Back to top

Auth Hooks (with react-query)

In this part of the documentation, we will be destructuring the example code from previous chapter.

Destructuring Logout Function

First, we'll destructure the function for logging out. Create a hooks folder in src and create UseLogout.js, which will contain the following code:

importPBfrom"lib/pocketbase";import{useState}from"react";functionUseLogout(){/* Later, this will be use to re-render the Auth component */const[dummy,setDummy]=useState(0);functionlogout(){/* logout() function was not an asynchronous function because it doesn't do an API call. Instead, it simply clear the cookie */PB.authStore.clear();setDummy(Math.random());}returnlogout;}exportdefaultUseLogout;

Note: The code above was an example of React custom hooks.


The Auth.js will look like this:

/* The custom hook was imported to Auth.js */importUseLogoutfrom"hooks/UseLogout";importPBfrom"lib/pocketbase";import{useState,useEffect}from"react";import{useForm}from"react-hook-form";functionAuth(){/* logout() function was declared like this */constlogout=UseLogout();const{ register, handleSubmit }=useForm();const[is_loading,setLoading]=useState(false);constis_logged_in=PB.authStore.isValid;asyncfunctionlogin(data){/* This function was a custom function of handleSubmit() */setLoading(true);try{constauth_data=awaitPB.collection("users").authWithPassword(data.email,data.password);}catch(error){console.log(error);}setLoading(false);}if(is_logged_in){return(<>{/* If a user has logged in, display the email address */}<h1>Logged In: {is_logged_in&&PB.authStore.model.email}</h1><buttononClick={logout}>Log Out</button></>);}return(<><h1>Welcome to Login Page!</h1>{is_loading&&<p>Loading...</p>}<formonSubmit={handleSubmit(login)}><inputtype="text"placeholder="email"{...register("email")}/><inputtype="password"placeholder="password"{...register("password")}/><buttontype="submit"disabled={is_loading}>Login</button></form></>);}exportdefaultAuth;

If you try this, it will work well, but the same problem as in the previous chapter will remain: the credentials you've inputted will remain.

To resolve this, add reset in the destructuring of useForm():

const{ register, handleSubmit, reset }=useForm();

Then, add this reset() function to the login function:

asyncfunctionlogin(data){/* This function was a custom function of handleSubmit() */setLoading(true);try{constauth_data=awaitPB.collection("users").authWithPassword(data.email,data.password);}catch(error){console.log(error);}setLoading(false);/* reset() was added here */reset();}

Destructuring Login Function

Now that we've destructured the logout function using a custom hook, we'll take a few steps to destructure the login as well.

Create UseLogin.js and it will contain the following:

importPBfrom"lib/pocketbase";import{useState}from"react";functionUseLogin(){const[is_loading,setLoading]=useState(false);asyncfunctionlogin({ email, password }){/* This function was a custom function of handleSubmit() */setLoading(true);try{constauth_data=awaitPB.collection("users").authWithPassword(email,password);}catch(error){console.log(error);}setLoading(false);}return{ login, is_loading };}exportdefaultUseLogin;

Since we removed the login function, Auth.js will look like this:

importUseLoginfrom"hooks/UseLogin";importUseLogoutfrom"hooks/UseLogout";importPBfrom"lib/pocketbase";import{useForm}from"react-hook-form";functionAuth(){constlogout=UseLogout();const{ login, is_loading }=UseLogin();const{ register, handleSubmit, reset }=useForm();constis_logged_in=PB.authStore.isValid;asyncfunctiononSubmit(data){login(data);reset();}if(is_logged_in){return(<>{/* If a user has logged in, display the email address */}<h1>Logged In: {is_logged_in&&PB.authStore.model.email}</h1><buttononClick={logout}>Log Out</button></>);}return(<><h1>Welcome to Login Page!</h1>{is_loading&&<p>Loading...</p>}<formonSubmit={handleSubmit(onSubmit)}><inputtype="text"placeholder="email"{...register("email")}/><inputtype="password"placeholder="password"{...register("password")}/><buttontype="submit"disabled={is_loading}>Login</button></form></>);}exportdefaultAuth;

Destructuring API call to PocketBase

In this part of the documentation, we will be using the react-query, and to install this library, run this command:

npm install react-query

Before we proceed to destructuring, we have to do the following on App.js to be able to implement the react-query and do the destructuring:

importAuthfrom"Auth";/* Import the necessary functions */import{QueryClientProvider,QueryClient}from"react-query";/* Initialize the QueryClient */constqueryClient=newQueryClient();functionApp(){return({/* Wrap the Auth component with QueryClientProvider and use queryClient as client */}<QueryClientProviderclient={ queryClient }><Auth/></QueryClientProvider>);}exportdefaultApp;

UseLogin.js will look like this after the destructuring:

importPBfrom"lib/pocketbase";/* useMutation was imported */import{useMutation}from"react-query"/* useState for is_loading was removed because react-query has built-in and it was isLoading*/functionUseLogin(){/* Because login() function was used as argument to useMutation, we no longer need to wrap it with try catch */asyncfunctionlogin({ email, password }){/* This function was a custom function of handleSubmit() */constauth_data=awaitPB.collection("users").authWithPassword(email,password);}/* login() was passed in useMutation as argument */returnuseMutation(login)}exportdefaultUseLogin;

And because of these changes, Auth.js will look like this:

importUseLoginfrom"hooks/UseLogin";importUseLogoutfrom"hooks/UseLogout";importPBfrom"lib/pocketbase";import{useForm}from"react-hook-form";functionAuth(){constlogout=UseLogout();const{/* "mutate" will be used to invoke the login() function */
mutate,/* "is_loading" is now isLoading() which is available to useMutation() by default */
isLoading,/* isError was also available to useMutation() by default */
isError }=UseLogin();const{ register, handleSubmit, reset }=useForm();constis_logged_in=PB.authStore.isValid;asyncfunctiononSubmit(data){mutate(data);reset();}if(is_logged_in){return(<>{/* If a user has logged in, display the email address */}<h1>Logged In: {is_logged_in&&PB.authStore.model.email}</h1><buttononClick={logout}>Log Out</button></>);}return(<><h1>Welcome to Login Page!</h1>{isLoading&&<p>Loading...</p>}{isError&&<p>Invalid email or password</p>}<formonSubmit={handleSubmit(onSubmit)}><inputtype="text"placeholder="email"{...register("email")}/><inputtype="password"placeholder="password"{...register("password")}/><buttontype="submit"disabled={isLoading}>Login</button></form></>);}exportdefaultAuth;

If you want to read more about react-query, here is the official documentation.

Back to top

Sending Verification Emails with Brevo (formerly Sendinblue) SMTP

In this part of the experiment, we will be using Sendinblue to send emails. If you don't have account in 'Sendinblue', you may create your own in this link.

In this part of the experiment, we will be using Sendinblue to send emails. If you don't have an account at 'Sendinblue', you may create one at this link.

Setting up your Sendinblue Account

Once you are done creating your own account, you should see this page: image


Now, click Transactional and then Settings in the left navigation bar. You should be able to see this page: image


Once you are there, click Configuration and Get Your SMTP Key. You should be on this page. Sendinblue


Now, click Generate a new SMTP key. A modal should appear, and you may enter any key you want. In my case, I use lab_exp. Then click the Generate button.

After you click the Generate button, the SMTP key of Sendinblue should appear; copy that and don't lose it.

Setting up an SMTP Server in PocketBase

To set up SMTP in PocketBase, go to the admin UI home page. Then click the setting icon and click on Mail Settings. You should be able to see this page:

image


Now, toggle Use SMTP mail server (recommended). A user interface for configuring your SMTP mail server would appear. Remember the SMTP key you copied earlier? You'll be pasting that in the password field. image


For the username field, go back to Sendinblue and close the modal for the SMTP key. Copy the login: Sendinblue(1)


For the SMTP Server Host field, paste smtp-relay.sendinblue.com, which can be seen back on the Sendinblue page. Then you may now click Save changes.

Send test email will appear, and when you click that, you can test if the configuration you made is working. Sendinblue(2)

Error in Setting up PocketBase Mail Settings

This is an optional part of the documentation in case you encountered an error while setting up the mail settings of PocketBase.

During the experiment at the lab, I encounter this error: pb-error

To resolve this issue, delete the following files:

  • CHANGELOG.md
  • LICENSE.md
  • pocketbase.exe
  • pb_data

Since you deleted the pb_data. You have to repeat the configuration of PocketBase mail settings and create an admin account for the Admin UI. You may read the Dangers of Deleting pb_data included in this documentation.

PocketBase Collection Types

In PocketBase, there are two types of collections:

  • Base Collection - is the default collection type, and you can use it for any type of data.
  • Auth Collection - contains extra fields to manage users, like username, email, and verified. An example of an Auth collection is the PocketBase default user collections.

This was discussed because later we will be creating a simple way of authenticating users.

Source:https://www.makeuseof.com/pocketbase-what-and-how/#:~:text=Creating%20a%20Collection%20in%20PocketBase&text=A%20collection%20can%20either%20be,username%2C%20email%2C%20and%20verified.

Getting the Records

In getting the records into PocketBase, here is an example from the official documentation:

importPocketBasefrom'pocketbase';constpb=newPocketBase('http://127.0.0.1:8090');
...
constrecord1=awaitpb.collection('posts').getOne('RECORD_ID',{expand: 'relField1,relField2.subRelField',});

This is how we implement it in hooks/UseVerified.js:

constuser_id=PB.authStore.model.id;constuser_data=awaitPB.collection("users").getOne(user_id);setIsVerified(user_data.verified);

Sending the Email Verification

To start off, we need the following:

  • First, a button that will send email verification
  • Then, a function will handle the click event for sending email verification.
  • Lastly, an indicator of whether the user is verified or not

Earlier, UseVerified.js was mentioned; add the following function to that file:

asyncfunctionrequestVerification(){constemail=PB.authStore.model.email;constresponse=awaitPB.collection("users").requestVerification(email);if(response){alert("Verification email sent! Check your inbox.");}}

Now, we're done with the function that will handle the sending of emails. In Auth.js, import the UseVerified custom hook:

importUseVerifiedfrom"hooks/UseVerified";
...
const{ is_verified, requestVerification }=UseVerified();

Now, create the indicator and the button in Auth.js:

<p>Verified: {is_verified.toString()}</p>{!is_verified&&<buttononClick={requestVerification}>Send Verification</button>}

Your UseVerified.js should look like the following:

import{useState,useEffect}from"react";importPBfrom"lib/pocketbase";functionUseVerified(){const[is_verified,setIsVerified]=useState(false);useEffect(()=>{asyncfunctioncheckVerified(){constuser_id=PB.authStore.model.id;constuser_data=awaitPB.collection("users").getOne(user_id);console.log("Fetch data: ",user_data);setIsVerified(user_data.verified);}constis_logged_in=PB.authStore.isValid;/* Invoke checkVerified() function if the user is logged in */if(is_logged_in){checkVerified();}},[]);asyncfunctionrequestVerification(){constemail=PB.authStore.model.email;constresponse=awaitPB.collection("users").requestVerification(email);if(response){alert("Verification email sent! Check your inbox.");}}return{ is_verified, requestVerification };}exportdefaultUseVerified;

In testing it, you should use your email account, and you should be able to receive this email once you test it: Verified

Click the Verify button in the email, return to your React app, and refresh it. Verified should have a value of true now.

Back to top

Automatic Re-fetching with the useQuery Hook

In this chapter of the documentation, we will be making changes to the app from the previous chapter and enabling it to reflect the changes that are happening in the database (PocketBase).

In order to do this, we will be using useQuery in React, and you may visit the official documentation by clicking this.

There will be a huge changes in UseVerified.js, first import the useQuery:

There will be huge changes in UseVerified.js. First, import useQuery.

import{useQuery}from"react-query";

Then UseVerified() will have a different return value:

returnuseQuery({queryFn: checkVerified,queryKey: ["check-verified",user_id]});

This is the last chapter in this documentation; you can click this to see the whole change in UseVerified.js. There were minor changes in Auth.js as well; you can click [this](https://github.com/MadrinanComLab/Exp-PocketBase/blob/master/pb_app/src/components/Auth.js to see the changes.

Explaining the Attributes of the useQuery Option

Keys in the option of useQuery was case sensitive. For example, you cannot make queryFn into queryFunction or other keys that you may wish to use.

The object that is used as an argument is called options, and each of its attributes has a different purpose:

  • queryFn - The function you will assign here will be responsible for fetching the data you need.

Note: Since checkVerified was used to be the value of queryFunction, the return value of it will change:

returnuser_data.verified
  • queryKey - This will be used for data caching, and the value of "check-verified" will be the key, and user_id is the second argument, which in this case will be used to query the user by its record id.

Note: The value of user_id will come from the code snippet below. Remember, this will have a value when you log in a user, then it becomes undefined when no user is logged in, and that is why optional chaining was implemented.

...
exportdefaultfunctionUseVerified(){constuser_id=PB.authStore.model?.id;
...
}

Back to top


Did You Like This Experiment?

Stay tuned for upcoming projects and experiments by following me on the following accounts:

About

This repository is an experiment that aims to learn about PocketBase and provide helpful notes to the lab and other developers who want to use PocketBase as a beginner.

Topics

Resources

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' GitHub - MadrinanComLab/Exp-PocketBase: This repository is an experiment that aims to learn about PocketBase and provide helpful notes to the lab and other developers who want to use PocketBase as a beginner. · GitHub
Skip to content

Latest commit

History

74 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Welcome to PocketBase Experiment!

PocketBase-Experiment

Reference for this experiment:https://www.youtube.com/watch?v=rediFff54JA&t=75s
This experiment was initiated on April 9, 2023.

Table of Contents

Introduction to PocketBase

PocketBase is similar to Firebase (a Google service) and Supabase (an open-source Firebase alternative), which handle backend functions like file upload, saving data to a database, etc.

I know that my introduction to PocketBase was too short to describe it, so here is the link to a video of Fireship where he fully introduces PocketBase.

You can download PocketBase using this link.

Back to top

How to Run PocketBase?

Once you've downloaded PocketBase and it was now in Downloads, do the following:
1. Extract the ZIP file that you downloaded.
2. Rename it PocketBase or however you like, but keep it short.
3. Go to that directory, e.g., Downloads/PocketBase.
4. Now enter this command:

./pocketbase serve

If the command above didn't work for you, try this:

pocketbase serve

Note: The pocketbase you see in the command is not the folder name, it was the keyword for the command.

5. After running the previous command you will see something like this:

2023/04/0922:11:46Serverstartedat http://127.0.0.1:8090REST API: http://127.0.0.1:8090/api/AdminUI: http://127.0.0.1:8090/_/

Back to top

Explaining the Links

Based on the official documentation, the meaning of the links are the following:
http://127.0.0.1:8090 - if pb_public directory exists, serves the static content from it (html, css, images, etc.)
http://127.0.0.1:8090/_/ - Admin dashboard UI
http://127.0.0.1:8090/api/ - REST API

Back to top

Folders in a Pre-Built Pocket Base

The prebuilt PocketBase executable will automatically create and manage 2 new directories alongside the executable:
pb_data - stores your application data, uploaded files, etc. (usually should be added in .gitignore).
pb_migrations - contains JS migration files with your collection changes (can be safely committed in your repository).

Back to top

User-Defined Folder

You have to add the following folder:
pb_public - This is the folder that will be hosted by PocketBase. Here is an example:
1. After creating the pb_public folder, create a file inside this folder named index.html and copy and paste the following code:

<!DOCTYPE html><htmllang="en"><head><metacharset="UTF-8"><metahttp-equiv="X-UA-Compatible" content="IE=edge"><metaname="viewport" content="width=device-width, initial-scale=1.0"><title>Testing123</title></head><body><h1>Welcome to PocketBase Experiment by MadriñanComLab!</h1></body></html>

2. Then go to this URL:

http://127.0.0.1:8090/

Back to top

Adding a React Application to a PocketBase App:

To add the React app, just do the following command:

npm create react-app <app_name>

Example:

npm create react-app pb_app

Back to top

Regaining Access to the PocketBase Admin UI

Just in case you have forgotten your password. What you can do is:
1. Stop the server of PocketBase on your computer (if it is running).
2. Delete the pb_data
3. Run your PocketBase
4. Go to http://127.0.0.1:8090/_/
5. Then you can now set your new email and password.

Back to top

Dangers of Deleting pb_data

Deleting this folder was helpful when you forgot your password for the admin UI, but deleting it will also affect the configuration you set in your PocketBase application.

Later on, configuring the mail settings of PocketBase will be discussed in this documentation.

Additional Note: By deleting the pb_data, you are deleting the data in your database. DO NOT delete pb_migration because this contains the structure of your database. You may delete it unless you intend to delete the structure of the database. But to just regain access to the admin side of your database, just delete the pb_data.

Back to top

Creating a New Collection

The admin UI was straightforward, but in case you are hesitant, here is a brief introduction: Steps-1-to-31. Start with creating your collection. After you click that, a modal will appear on the right.
2. Name your collection however you want. Think of a collection as a table, and you can see in the suggestion that it follows the naming convention of SQL (where tables are named plural).
3. After naming your first collection, you can then create the first field in your collection.

Steps-4-to-54. After clicking the New field button, options for data type would appear.
5. Name your first field here. Think of a field as a column in your database.
Steps-66. If you click the cog icon inline with your field, an option for min and max length would appear as well as the regex.
Steps-7PB-Index-Modal7. If you click "New index," a modal will appear for you to setup your indexes.

Unfamiliar with indexes? You can read about it here

8. You can now save your first collection. Ow, wait, you aren't able to save it? Well, that was because there is already a "users" collection that was initially included in your first PocketBase application.

I apologize about it; this README was created while first exploring PocketBase.

Back to top

React and PocketBase SDK Setup

In the tutorial, the following files were added to the root folder of pb_app:
.env.local
jsconfig.json - In the code here, what it does is make the src folder the root directory for importing and avoid the example imports below.:

importxfrom"../../../folder/file";

Delete the following in pb_app/src:
App.css
App.test.js
index.css
logo.svg
reportWebVitals.js
setupTest.js
Remove the following in index.js:

importreportWebVitalsfrom'./reportWebVitals';import'./index.css';
...
// If you want to start measuring performance in your app, pass a function// to log results (for example: reportWebVitals(console.log))// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitalsreportWebVitals();

Remove the following in App.js:

importlogofrom'./logo.svg';import'./App.css';
...
// This is the chuck of code in return statement<divclassName="App"><headerclassName="App-header"><imgsrc={logo}className="App-logo"alt="logo"/><p>
Edit <code>src/App.js</code> and save to reload.
</p><aclassName="App-link"href="https://reactjs.org"target="_blank"rel="noopener noreferrer">
Learn React
</a></header></div>

You can now add the JavaScript PocketBase SDK by running the following command in your terminal:

npm install pocketbase --save

Note: Make sure you are in the pb_app directory, where the React app is.

Then create the /lib/pocketbase.js file inside the src folder and add the following code snippet:

importPocketBasefrom"pocketbase";constpb=newPocketBase(process.env.REACT_APP_PB_URL);exportdefaultpb;

Note: The value of REACT_APP_PB_URL was http://127.0.0.1:8090, and it was in the .env.local

Now, to test if everything is working, run the following command:

npm start

Back to top

Authentication (with react-hook-form)

PocketBase provides documentation about their authentication; you can read it here if you want to read more about it.

Examples

The following will be a set of different examples of PocketBase authentication.

#1

First, create Auth.js in src. For now, the Auth component will contain the following:

importPBfrom"lib/pocketbase";functionAuth(){return(<>{/* For a simple example of pocketbase authentication */}<h1>Logged In: {PB.authStore.isValid.toString()}</h1></>);}exportdefaultAuth;

Import the Auth component to App.js:

importAuthfrom"Auth";functionApp(){return(<><Auth/></>);}exportdefaultApp;

Your browser should display something like this: ss-3

The first example makes sense that it displays false because a user hasn't logged in yet, right?

#2

Now, let's level up the previous example. Still in Auth.js, do the following:

importPBfrom"lib/pocketbase";import{useState}from"react";import{useForm}from"react-hook-form";functionAuth(){const{ register, handleSubmit }=useForm();const[is_loading,setLoading]=useState(false);asyncfunctionlogin(data){/* This function was a custom function of handleSubmit() */setLoading(true);try{constauth_data=awaitPB.collection("users").authWithPassword(data.email,data.password);}catch(error){console.log(error);}setLoading(false);}return(<><h1>Logged In: {PB.authStore.isValid.toString()}</h1>{is_loading&&<p>Loading...</p>}<formonSubmit={handleSubmit(login)}><inputtype="text"placeholder="email"{...register("email")}/><inputtype="password"placeholder="password"{...register("password")}/><buttontype="submit"disabled={is_loading}>Login</button></form></>);}exportdefaultAuth;

Before we proceed with creating the first user, let's breakdown the example code above because there was a huge change between examples #1 and #2.

  • First is about the React-hook-form, which will help us handle the sample login:
const{ register, handleSubmit }=useForm();

To install react-hook-form in your project, just run the following command:

npm install react-hook-form

Note: Make sure you are in pb_app folder before you install this npm library.

  • State object that will be used for a simple loading indicator.
const[is_loading,setLoading]=useState(false);

  • Then, on the actual code for validation, there was a comment on the code to help break it down:
asyncfunctionlogin(data){/* This function was a custom function of handleSubmit() *//* Set is_loading to true to indicated the request in on process */setLoading(true);/* Try catch statement was used to handle the possible error that may occur. */try{constauth_data=awaitPB/* The 'collection' is the collection of pocketbase */.collection("users")/* 'authWithPassword' will handle the authentication */.authWithPassword(data.email,data.password);}catch(error){console.log(error);}setLoading(false);}

Now, let's create our first user. You can use whatever email and password you want: sample-usersample-user(1)

After this, you can try it to see if it works.

#3

Users can now log in, and for the last example, we will enable users to log out. Do this in Auth.js.:

importPBfrom"lib/pocketbase";import{useState,useEffect}from"react";import{useForm}from"react-hook-form";functionAuth(){const{ register, handleSubmit }=useForm();const[is_loading,setLoading]=useState(false);constis_logged_in=PB.authStore.isValid;/* Later, this will be use to re-render the Auth component */const[dummy,setDummy]=useState(0);asyncfunctionlogin(data){/* This function was a custom function of handleSubmit() */setLoading(true);try{constauth_data=awaitPB.collection("users").authWithPassword(data.email,data.password);}catch(error){console.log(error);}setLoading(false);}functionlogout(){/* logout() function was not an asynchronous function because it doesn't do an API call. Instead, it simply clear the cookie */PB.authStore.clear();setDummy(Math.random());}/* Just another way of conditional rendering in React */if(is_logged_in){return(<>{/* If a user has logged in, display the email address */}<h1>Logged In: {is_logged_in&&PB.authStore.model.email}</h1><buttononClick={logout}>Log Out</button></>);}return(<><h1>Welcome to Login Page!</h1>{is_loading&&<p>Loading...</p>}<formonSubmit={handleSubmit(login)}><inputtype="text"placeholder="email"{...register("email")}/><inputtype="password"placeholder="password"{...register("password")}/><buttontype="submit"disabled={is_loading}>Login</button></form></>);}exportdefaultAuth;

Back to top

Auth Hooks (with react-query)

In this part of the documentation, we will be destructuring the example code from previous chapter.

Destructuring Logout Function

First, we'll destructure the function for logging out. Create a hooks folder in src and create UseLogout.js, which will contain the following code:

importPBfrom"lib/pocketbase";import{useState}from"react";functionUseLogout(){/* Later, this will be use to re-render the Auth component */const[dummy,setDummy]=useState(0);functionlogout(){/* logout() function was not an asynchronous function because it doesn't do an API call. Instead, it simply clear the cookie */PB.authStore.clear();setDummy(Math.random());}returnlogout;}exportdefaultUseLogout;

Note: The code above was an example of React custom hooks.


The Auth.js will look like this:

/* The custom hook was imported to Auth.js */importUseLogoutfrom"hooks/UseLogout";importPBfrom"lib/pocketbase";import{useState,useEffect}from"react";import{useForm}from"react-hook-form";functionAuth(){/* logout() function was declared like this */constlogout=UseLogout();const{ register, handleSubmit }=useForm();const[is_loading,setLoading]=useState(false);constis_logged_in=PB.authStore.isValid;asyncfunctionlogin(data){/* This function was a custom function of handleSubmit() */setLoading(true);try{constauth_data=awaitPB.collection("users").authWithPassword(data.email,data.password);}catch(error){console.log(error);}setLoading(false);}if(is_logged_in){return(<>{/* If a user has logged in, display the email address */}<h1>Logged In: {is_logged_in&&PB.authStore.model.email}</h1><buttononClick={logout}>Log Out</button></>);}return(<><h1>Welcome to Login Page!</h1>{is_loading&&<p>Loading...</p>}<formonSubmit={handleSubmit(login)}><inputtype="text"placeholder="email"{...register("email")}/><inputtype="password"placeholder="password"{...register("password")}/><buttontype="submit"disabled={is_loading}>Login</button></form></>);}exportdefaultAuth;

If you try this, it will work well, but the same problem as in the previous chapter will remain: the credentials you've inputted will remain.

To resolve this, add reset in the destructuring of useForm():

const{ register, handleSubmit, reset }=useForm();

Then, add this reset() function to the login function:

asyncfunctionlogin(data){/* This function was a custom function of handleSubmit() */setLoading(true);try{constauth_data=awaitPB.collection("users").authWithPassword(data.email,data.password);}catch(error){console.log(error);}setLoading(false);/* reset() was added here */reset();}

Destructuring Login Function

Now that we've destructured the logout function using a custom hook, we'll take a few steps to destructure the login as well.

Create UseLogin.js and it will contain the following:

importPBfrom"lib/pocketbase";import{useState}from"react";functionUseLogin(){const[is_loading,setLoading]=useState(false);asyncfunctionlogin({ email, password }){/* This function was a custom function of handleSubmit() */setLoading(true);try{constauth_data=awaitPB.collection("users").authWithPassword(email,password);}catch(error){console.log(error);}setLoading(false);}return{ login, is_loading };}exportdefaultUseLogin;

Since we removed the login function, Auth.js will look like this:

importUseLoginfrom"hooks/UseLogin";importUseLogoutfrom"hooks/UseLogout";importPBfrom"lib/pocketbase";import{useForm}from"react-hook-form";functionAuth(){constlogout=UseLogout();const{ login, is_loading }=UseLogin();const{ register, handleSubmit, reset }=useForm();constis_logged_in=PB.authStore.isValid;asyncfunctiononSubmit(data){login(data);reset();}if(is_logged_in){return(<>{/* If a user has logged in, display the email address */}<h1>Logged In: {is_logged_in&&PB.authStore.model.email}</h1><buttononClick={logout}>Log Out</button></>);}return(<><h1>Welcome to Login Page!</h1>{is_loading&&<p>Loading...</p>}<formonSubmit={handleSubmit(onSubmit)}><inputtype="text"placeholder="email"{...register("email")}/><inputtype="password"placeholder="password"{...register("password")}/><buttontype="submit"disabled={is_loading}>Login</button></form></>);}exportdefaultAuth;

Destructuring API call to PocketBase

In this part of the documentation, we will be using the react-query, and to install this library, run this command:

npm install react-query

Before we proceed to destructuring, we have to do the following on App.js to be able to implement the react-query and do the destructuring:

importAuthfrom"Auth";/* Import the necessary functions */import{QueryClientProvider,QueryClient}from"react-query";/* Initialize the QueryClient */constqueryClient=newQueryClient();functionApp(){return({/* Wrap the Auth component with QueryClientProvider and use queryClient as client */}<QueryClientProviderclient={ queryClient }><Auth/></QueryClientProvider>);}exportdefaultApp;

UseLogin.js will look like this after the destructuring:

importPBfrom"lib/pocketbase";/* useMutation was imported */import{useMutation}from"react-query"/* useState for is_loading was removed because react-query has built-in and it was isLoading*/functionUseLogin(){/* Because login() function was used as argument to useMutation, we no longer need to wrap it with try catch */asyncfunctionlogin({ email, password }){/* This function was a custom function of handleSubmit() */constauth_data=awaitPB.collection("users").authWithPassword(email,password);}/* login() was passed in useMutation as argument */returnuseMutation(login)}exportdefaultUseLogin;

And because of these changes, Auth.js will look like this:

importUseLoginfrom"hooks/UseLogin";importUseLogoutfrom"hooks/UseLogout";importPBfrom"lib/pocketbase";import{useForm}from"react-hook-form";functionAuth(){constlogout=UseLogout();const{/* "mutate" will be used to invoke the login() function */
mutate,/* "is_loading" is now isLoading() which is available to useMutation() by default */
isLoading,/* isError was also available to useMutation() by default */
isError }=UseLogin();const{ register, handleSubmit, reset }=useForm();constis_logged_in=PB.authStore.isValid;asyncfunctiononSubmit(data){mutate(data);reset();}if(is_logged_in){return(<>{/* If a user has logged in, display the email address */}<h1>Logged In: {is_logged_in&&PB.authStore.model.email}</h1><buttononClick={logout}>Log Out</button></>);}return(<><h1>Welcome to Login Page!</h1>{isLoading&&<p>Loading...</p>}{isError&&<p>Invalid email or password</p>}<formonSubmit={handleSubmit(onSubmit)}><inputtype="text"placeholder="email"{...register("email")}/><inputtype="password"placeholder="password"{...register("password")}/><buttontype="submit"disabled={isLoading}>Login</button></form></>);}exportdefaultAuth;

If you want to read more about react-query, here is the official documentation.

Back to top

Sending Verification Emails with Brevo (formerly Sendinblue) SMTP

In this part of the experiment, we will be using Sendinblue to send emails. If you don't have account in 'Sendinblue', you may create your own in this link.

In this part of the experiment, we will be using Sendinblue to send emails. If you don't have an account at 'Sendinblue', you may create one at this link.

Setting up your Sendinblue Account

Once you are done creating your own account, you should see this page: image


Now, click Transactional and then Settings in the left navigation bar. You should be able to see this page: image


Once you are there, click Configuration and Get Your SMTP Key. You should be on this page. Sendinblue


Now, click Generate a new SMTP key. A modal should appear, and you may enter any key you want. In my case, I use lab_exp. Then click the Generate button.

After you click the Generate button, the SMTP key of Sendinblue should appear; copy that and don't lose it.

Setting up an SMTP Server in PocketBase

To set up SMTP in PocketBase, go to the admin UI home page. Then click the setting icon and click on Mail Settings. You should be able to see this page:

image


Now, toggle Use SMTP mail server (recommended). A user interface for configuring your SMTP mail server would appear. Remember the SMTP key you copied earlier? You'll be pasting that in the password field. image


For the username field, go back to Sendinblue and close the modal for the SMTP key. Copy the login: Sendinblue(1)


For the SMTP Server Host field, paste smtp-relay.sendinblue.com, which can be seen back on the Sendinblue page. Then you may now click Save changes.

Send test email will appear, and when you click that, you can test if the configuration you made is working. Sendinblue(2)

Error in Setting up PocketBase Mail Settings

This is an optional part of the documentation in case you encountered an error while setting up the mail settings of PocketBase.

During the experiment at the lab, I encounter this error: pb-error

To resolve this issue, delete the following files:

  • CHANGELOG.md
  • LICENSE.md
  • pocketbase.exe
  • pb_data

Since you deleted the pb_data. You have to repeat the configuration of PocketBase mail settings and create an admin account for the Admin UI. You may read the Dangers of Deleting pb_data included in this documentation.

PocketBase Collection Types

In PocketBase, there are two types of collections:

  • Base Collection - is the default collection type, and you can use it for any type of data.
  • Auth Collection - contains extra fields to manage users, like username, email, and verified. An example of an Auth collection is the PocketBase default user collections.

This was discussed because later we will be creating a simple way of authenticating users.

Source:https://www.makeuseof.com/pocketbase-what-and-how/#:~:text=Creating%20a%20Collection%20in%20PocketBase&text=A%20collection%20can%20either%20be,username%2C%20email%2C%20and%20verified.

Getting the Records

In getting the records into PocketBase, here is an example from the official documentation:

importPocketBasefrom'pocketbase';constpb=newPocketBase('http://127.0.0.1:8090');
...
constrecord1=awaitpb.collection('posts').getOne('RECORD_ID',{expand: 'relField1,relField2.subRelField',});

This is how we implement it in hooks/UseVerified.js:

constuser_id=PB.authStore.model.id;constuser_data=awaitPB.collection("users").getOne(user_id);setIsVerified(user_data.verified);

Sending the Email Verification

To start off, we need the following:

  • First, a button that will send email verification
  • Then, a function will handle the click event for sending email verification.
  • Lastly, an indicator of whether the user is verified or not

Earlier, UseVerified.js was mentioned; add the following function to that file:

asyncfunctionrequestVerification(){constemail=PB.authStore.model.email;constresponse=awaitPB.collection("users").requestVerification(email);if(response){alert("Verification email sent! Check your inbox.");}}

Now, we're done with the function that will handle the sending of emails. In Auth.js, import the UseVerified custom hook:

importUseVerifiedfrom"hooks/UseVerified";
...
const{ is_verified, requestVerification }=UseVerified();

Now, create the indicator and the button in Auth.js:

<p>Verified: {is_verified.toString()}</p>{!is_verified&&<buttononClick={requestVerification}>Send Verification</button>}

Your UseVerified.js should look like the following:

import{useState,useEffect}from"react";importPBfrom"lib/pocketbase";functionUseVerified(){const[is_verified,setIsVerified]=useState(false);useEffect(()=>{asyncfunctioncheckVerified(){constuser_id=PB.authStore.model.id;constuser_data=awaitPB.collection("users").getOne(user_id);console.log("Fetch data: ",user_data);setIsVerified(user_data.verified);}constis_logged_in=PB.authStore.isValid;/* Invoke checkVerified() function if the user is logged in */if(is_logged_in){checkVerified();}},[]);asyncfunctionrequestVerification(){constemail=PB.authStore.model.email;constresponse=awaitPB.collection("users").requestVerification(email);if(response){alert("Verification email sent! Check your inbox.");}}return{ is_verified, requestVerification };}exportdefaultUseVerified;

In testing it, you should use your email account, and you should be able to receive this email once you test it: Verified

Click the Verify button in the email, return to your React app, and refresh it. Verified should have a value of true now.

Back to top

Automatic Re-fetching with the useQuery Hook

In this chapter of the documentation, we will be making changes to the app from the previous chapter and enabling it to reflect the changes that are happening in the database (PocketBase).

In order to do this, we will be using useQuery in React, and you may visit the official documentation by clicking this.

There will be a huge changes in UseVerified.js, first import the useQuery:

There will be huge changes in UseVerified.js. First, import useQuery.

import{useQuery}from"react-query";

Then UseVerified() will have a different return value:

returnuseQuery({queryFn: checkVerified,queryKey: ["check-verified",user_id]});

This is the last chapter in this documentation; you can click this to see the whole change in UseVerified.js. There were minor changes in Auth.js as well; you can click [this](https://github.com/MadrinanComLab/Exp-PocketBase/blob/master/pb_app/src/components/Auth.js to see the changes.

Explaining the Attributes of the useQuery Option

Keys in the option of useQuery was case sensitive. For example, you cannot make queryFn into queryFunction or other keys that you may wish to use.

The object that is used as an argument is called options, and each of its attributes has a different purpose:

  • queryFn - The function you will assign here will be responsible for fetching the data you need.

Note: Since checkVerified was used to be the value of queryFunction, the return value of it will change:

returnuser_data.verified
  • queryKey - This will be used for data caching, and the value of "check-verified" will be the key, and user_id is the second argument, which in this case will be used to query the user by its record id.

Note: The value of user_id will come from the code snippet below. Remember, this will have a value when you log in a user, then it becomes undefined when no user is logged in, and that is why optional chaining was implemented.

...
exportdefaultfunctionUseVerified(){constuser_id=PB.authStore.model?.id;
...
}

Back to top


Did You Like This Experiment?

Stay tuned for upcoming projects and experiments by following me on the following accounts:

About

This repository is an experiment that aims to learn about PocketBase and provide helpful notes to the lab and other developers who want to use PocketBase as a beginner.

Topics

Resources

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - MadrinanComLab/Exp-PocketBase: This repository is an experiment that aims to learn about PocketBase and provide helpful notes to the lab and other developers who want to use PocketBase as a beginner. · GitHub
Skip to content

Latest commit

History

74 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Welcome to PocketBase Experiment!

PocketBase-Experiment

Reference for this experiment:https://www.youtube.com/watch?v=rediFff54JA&t=75s
This experiment was initiated on April 9, 2023.

Table of Contents

Introduction to PocketBase

PocketBase is similar to Firebase (a Google service) and Supabase (an open-source Firebase alternative), which handle backend functions like file upload, saving data to a database, etc.

I know that my introduction to PocketBase was too short to describe it, so here is the link to a video of Fireship where he fully introduces PocketBase.

You can download PocketBase using this link.

Back to top

How to Run PocketBase?

Once you've downloaded PocketBase and it was now in Downloads, do the following:
1. Extract the ZIP file that you downloaded.
2. Rename it PocketBase or however you like, but keep it short.
3. Go to that directory, e.g., Downloads/PocketBase.
4. Now enter this command:

./pocketbase serve

If the command above didn't work for you, try this:

pocketbase serve

Note: The pocketbase you see in the command is not the folder name, it was the keyword for the command.

5. After running the previous command you will see something like this:

2023/04/0922:11:46Serverstartedat http://127.0.0.1:8090REST API: http://127.0.0.1:8090/api/AdminUI: http://127.0.0.1:8090/_/

Back to top

Explaining the Links

Based on the official documentation, the meaning of the links are the following:
http://127.0.0.1:8090 - if pb_public directory exists, serves the static content from it (html, css, images, etc.)
http://127.0.0.1:8090/_/ - Admin dashboard UI
http://127.0.0.1:8090/api/ - REST API

Back to top

Folders in a Pre-Built Pocket Base

The prebuilt PocketBase executable will automatically create and manage 2 new directories alongside the executable:
pb_data - stores your application data, uploaded files, etc. (usually should be added in .gitignore).
pb_migrations - contains JS migration files with your collection changes (can be safely committed in your repository).

Back to top

User-Defined Folder

You have to add the following folder:
pb_public - This is the folder that will be hosted by PocketBase. Here is an example:
1. After creating the pb_public folder, create a file inside this folder named index.html and copy and paste the following code:

<!DOCTYPE html><htmllang="en"><head><metacharset="UTF-8"><metahttp-equiv="X-UA-Compatible" content="IE=edge"><metaname="viewport" content="width=device-width, initial-scale=1.0"><title>Testing123</title></head><body><h1>Welcome to PocketBase Experiment by MadriñanComLab!</h1></body></html>

2. Then go to this URL:

http://127.0.0.1:8090/

Back to top

Adding a React Application to a PocketBase App:

To add the React app, just do the following command:

npm create react-app <app_name>

Example:

npm create react-app pb_app

Back to top

Regaining Access to the PocketBase Admin UI

Just in case you have forgotten your password. What you can do is:
1. Stop the server of PocketBase on your computer (if it is running).
2. Delete the pb_data
3. Run your PocketBase
4. Go to http://127.0.0.1:8090/_/
5. Then you can now set your new email and password.

Back to top

Dangers of Deleting pb_data

Deleting this folder was helpful when you forgot your password for the admin UI, but deleting it will also affect the configuration you set in your PocketBase application.

Later on, configuring the mail settings of PocketBase will be discussed in this documentation.

Additional Note: By deleting the pb_data, you are deleting the data in your database. DO NOT delete pb_migration because this contains the structure of your database. You may delete it unless you intend to delete the structure of the database. But to just regain access to the admin side of your database, just delete the pb_data.

Back to top

Creating a New Collection

The admin UI was straightforward, but in case you are hesitant, here is a brief introduction: Steps-1-to-31. Start with creating your collection. After you click that, a modal will appear on the right.
2. Name your collection however you want. Think of a collection as a table, and you can see in the suggestion that it follows the naming convention of SQL (where tables are named plural).
3. After naming your first collection, you can then create the first field in your collection.

Steps-4-to-54. After clicking the New field button, options for data type would appear.
5. Name your first field here. Think of a field as a column in your database.
Steps-66. If you click the cog icon inline with your field, an option for min and max length would appear as well as the regex.
Steps-7PB-Index-Modal7. If you click "New index," a modal will appear for you to setup your indexes.

Unfamiliar with indexes? You can read about it here

8. You can now save your first collection. Ow, wait, you aren't able to save it? Well, that was because there is already a "users" collection that was initially included in your first PocketBase application.

I apologize about it; this README was created while first exploring PocketBase.

Back to top

React and PocketBase SDK Setup

In the tutorial, the following files were added to the root folder of pb_app:
.env.local
jsconfig.json - In the code here, what it does is make the src folder the root directory for importing and avoid the example imports below.:

importxfrom"../../../folder/file";

Delete the following in pb_app/src:
App.css
App.test.js
index.css
logo.svg
reportWebVitals.js
setupTest.js
Remove the following in index.js:

importreportWebVitalsfrom'./reportWebVitals';import'./index.css';
...
// If you want to start measuring performance in your app, pass a function// to log results (for example: reportWebVitals(console.log))// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitalsreportWebVitals();

Remove the following in App.js:

importlogofrom'./logo.svg';import'./App.css';
...
// This is the chuck of code in return statement<divclassName="App"><headerclassName="App-header"><imgsrc={logo}className="App-logo"alt="logo"/><p>
Edit <code>src/App.js</code> and save to reload.
</p><aclassName="App-link"href="https://reactjs.org"target="_blank"rel="noopener noreferrer">
Learn React
</a></header></div>

You can now add the JavaScript PocketBase SDK by running the following command in your terminal:

npm install pocketbase --save

Note: Make sure you are in the pb_app directory, where the React app is.

Then create the /lib/pocketbase.js file inside the src folder and add the following code snippet:

importPocketBasefrom"pocketbase";constpb=newPocketBase(process.env.REACT_APP_PB_URL);exportdefaultpb;

Note: The value of REACT_APP_PB_URL was http://127.0.0.1:8090, and it was in the .env.local

Now, to test if everything is working, run the following command:

npm start

Back to top

Authentication (with react-hook-form)

PocketBase provides documentation about their authentication; you can read it here if you want to read more about it.

Examples

The following will be a set of different examples of PocketBase authentication.

#1

First, create Auth.js in src. For now, the Auth component will contain the following:

importPBfrom"lib/pocketbase";functionAuth(){return(<>{/* For a simple example of pocketbase authentication */}<h1>Logged In: {PB.authStore.isValid.toString()}</h1></>);}exportdefaultAuth;

Import the Auth component to App.js:

importAuthfrom"Auth";functionApp(){return(<><Auth/></>);}exportdefaultApp;

Your browser should display something like this: ss-3

The first example makes sense that it displays false because a user hasn't logged in yet, right?

#2

Now, let's level up the previous example. Still in Auth.js, do the following:

importPBfrom"lib/pocketbase";import{useState}from"react";import{useForm}from"react-hook-form";functionAuth(){const{ register, handleSubmit }=useForm();const[is_loading,setLoading]=useState(false);asyncfunctionlogin(data){/* This function was a custom function of handleSubmit() */setLoading(true);try{constauth_data=awaitPB.collection("users").authWithPassword(data.email,data.password);}catch(error){console.log(error);}setLoading(false);}return(<><h1>Logged In: {PB.authStore.isValid.toString()}</h1>{is_loading&&<p>Loading...</p>}<formonSubmit={handleSubmit(login)}><inputtype="text"placeholder="email"{...register("email")}/><inputtype="password"placeholder="password"{...register("password")}/><buttontype="submit"disabled={is_loading}>Login</button></form></>);}exportdefaultAuth;

Before we proceed with creating the first user, let's breakdown the example code above because there was a huge change between examples #1 and #2.

  • First is about the React-hook-form, which will help us handle the sample login:
const{ register, handleSubmit }=useForm();

To install react-hook-form in your project, just run the following command:

npm install react-hook-form

Note: Make sure you are in pb_app folder before you install this npm library.

  • State object that will be used for a simple loading indicator.
const[is_loading,setLoading]=useState(false);

  • Then, on the actual code for validation, there was a comment on the code to help break it down:
asyncfunctionlogin(data){/* This function was a custom function of handleSubmit() *//* Set is_loading to true to indicated the request in on process */setLoading(true);/* Try catch statement was used to handle the possible error that may occur. */try{constauth_data=awaitPB/* The 'collection' is the collection of pocketbase */.collection("users")/* 'authWithPassword' will handle the authentication */.authWithPassword(data.email,data.password);}catch(error){console.log(error);}setLoading(false);}

Now, let's create our first user. You can use whatever email and password you want: sample-usersample-user(1)

After this, you can try it to see if it works.

#3

Users can now log in, and for the last example, we will enable users to log out. Do this in Auth.js.:

importPBfrom"lib/pocketbase";import{useState,useEffect}from"react";import{useForm}from"react-hook-form";functionAuth(){const{ register, handleSubmit }=useForm();const[is_loading,setLoading]=useState(false);constis_logged_in=PB.authStore.isValid;/* Later, this will be use to re-render the Auth component */const[dummy,setDummy]=useState(0);asyncfunctionlogin(data){/* This function was a custom function of handleSubmit() */setLoading(true);try{constauth_data=awaitPB.collection("users").authWithPassword(data.email,data.password);}catch(error){console.log(error);}setLoading(false);}functionlogout(){/* logout() function was not an asynchronous function because it doesn't do an API call. Instead, it simply clear the cookie */PB.authStore.clear();setDummy(Math.random());}/* Just another way of conditional rendering in React */if(is_logged_in){return(<>{/* If a user has logged in, display the email address */}<h1>Logged In: {is_logged_in&&PB.authStore.model.email}</h1><buttononClick={logout}>Log Out</button></>);}return(<><h1>Welcome to Login Page!</h1>{is_loading&&<p>Loading...</p>}<formonSubmit={handleSubmit(login)}><inputtype="text"placeholder="email"{...register("email")}/><inputtype="password"placeholder="password"{...register("password")}/><buttontype="submit"disabled={is_loading}>Login</button></form></>);}exportdefaultAuth;

Back to top

Auth Hooks (with react-query)

In this part of the documentation, we will be destructuring the example code from previous chapter.

Destructuring Logout Function

First, we'll destructure the function for logging out. Create a hooks folder in src and create UseLogout.js, which will contain the following code:

importPBfrom"lib/pocketbase";import{useState}from"react";functionUseLogout(){/* Later, this will be use to re-render the Auth component */const[dummy,setDummy]=useState(0);functionlogout(){/* logout() function was not an asynchronous function because it doesn't do an API call. Instead, it simply clear the cookie */PB.authStore.clear();setDummy(Math.random());}returnlogout;}exportdefaultUseLogout;

Note: The code above was an example of React custom hooks.


The Auth.js will look like this:

/* The custom hook was imported to Auth.js */importUseLogoutfrom"hooks/UseLogout";importPBfrom"lib/pocketbase";import{useState,useEffect}from"react";import{useForm}from"react-hook-form";functionAuth(){/* logout() function was declared like this */constlogout=UseLogout();const{ register, handleSubmit }=useForm();const[is_loading,setLoading]=useState(false);constis_logged_in=PB.authStore.isValid;asyncfunctionlogin(data){/* This function was a custom function of handleSubmit() */setLoading(true);try{constauth_data=awaitPB.collection("users").authWithPassword(data.email,data.password);}catch(error){console.log(error);}setLoading(false);}if(is_logged_in){return(<>{/* If a user has logged in, display the email address */}<h1>Logged In: {is_logged_in&&PB.authStore.model.email}</h1><buttononClick={logout}>Log Out</button></>);}return(<><h1>Welcome to Login Page!</h1>{is_loading&&<p>Loading...</p>}<formonSubmit={handleSubmit(login)}><inputtype="text"placeholder="email"{...register("email")}/><inputtype="password"placeholder="password"{...register("password")}/><buttontype="submit"disabled={is_loading}>Login</button></form></>);}exportdefaultAuth;

If you try this, it will work well, but the same problem as in the previous chapter will remain: the credentials you've inputted will remain.

To resolve this, add reset in the destructuring of useForm():

const{ register, handleSubmit, reset }=useForm();

Then, add this reset() function to the login function:

asyncfunctionlogin(data){/* This function was a custom function of handleSubmit() */setLoading(true);try{constauth_data=awaitPB.collection("users").authWithPassword(data.email,data.password);}catch(error){console.log(error);}setLoading(false);/* reset() was added here */reset();}

Destructuring Login Function

Now that we've destructured the logout function using a custom hook, we'll take a few steps to destructure the login as well.

Create UseLogin.js and it will contain the following:

importPBfrom"lib/pocketbase";import{useState}from"react";functionUseLogin(){const[is_loading,setLoading]=useState(false);asyncfunctionlogin({ email, password }){/* This function was a custom function of handleSubmit() */setLoading(true);try{constauth_data=awaitPB.collection("users").authWithPassword(email,password);}catch(error){console.log(error);}setLoading(false);}return{ login, is_loading };}exportdefaultUseLogin;

Since we removed the login function, Auth.js will look like this:

importUseLoginfrom"hooks/UseLogin";importUseLogoutfrom"hooks/UseLogout";importPBfrom"lib/pocketbase";import{useForm}from"react-hook-form";functionAuth(){constlogout=UseLogout();const{ login, is_loading }=UseLogin();const{ register, handleSubmit, reset }=useForm();constis_logged_in=PB.authStore.isValid;asyncfunctiononSubmit(data){login(data);reset();}if(is_logged_in){return(<>{/* If a user has logged in, display the email address */}<h1>Logged In: {is_logged_in&&PB.authStore.model.email}</h1><buttononClick={logout}>Log Out</button></>);}return(<><h1>Welcome to Login Page!</h1>{is_loading&&<p>Loading...</p>}<formonSubmit={handleSubmit(onSubmit)}><inputtype="text"placeholder="email"{...register("email")}/><inputtype="password"placeholder="password"{...register("password")}/><buttontype="submit"disabled={is_loading}>Login</button></form></>);}exportdefaultAuth;

Destructuring API call to PocketBase

In this part of the documentation, we will be using the react-query, and to install this library, run this command:

npm install react-query

Before we proceed to destructuring, we have to do the following on App.js to be able to implement the react-query and do the destructuring:

importAuthfrom"Auth";/* Import the necessary functions */import{QueryClientProvider,QueryClient}from"react-query";/* Initialize the QueryClient */constqueryClient=newQueryClient();functionApp(){return({/* Wrap the Auth component with QueryClientProvider and use queryClient as client */}<QueryClientProviderclient={ queryClient }><Auth/></QueryClientProvider>);}exportdefaultApp;

UseLogin.js will look like this after the destructuring:

importPBfrom"lib/pocketbase";/* useMutation was imported */import{useMutation}from"react-query"/* useState for is_loading was removed because react-query has built-in and it was isLoading*/functionUseLogin(){/* Because login() function was used as argument to useMutation, we no longer need to wrap it with try catch */asyncfunctionlogin({ email, password }){/* This function was a custom function of handleSubmit() */constauth_data=awaitPB.collection("users").authWithPassword(email,password);}/* login() was passed in useMutation as argument */returnuseMutation(login)}exportdefaultUseLogin;

And because of these changes, Auth.js will look like this:

importUseLoginfrom"hooks/UseLogin";importUseLogoutfrom"hooks/UseLogout";importPBfrom"lib/pocketbase";import{useForm}from"react-hook-form";functionAuth(){constlogout=UseLogout();const{/* "mutate" will be used to invoke the login() function */
mutate,/* "is_loading" is now isLoading() which is available to useMutation() by default */
isLoading,/* isError was also available to useMutation() by default */
isError }=UseLogin();const{ register, handleSubmit, reset }=useForm();constis_logged_in=PB.authStore.isValid;asyncfunctiononSubmit(data){mutate(data);reset();}if(is_logged_in){return(<>{/* If a user has logged in, display the email address */}<h1>Logged In: {is_logged_in&&PB.authStore.model.email}</h1><buttononClick={logout}>Log Out</button></>);}return(<><h1>Welcome to Login Page!</h1>{isLoading&&<p>Loading...</p>}{isError&&<p>Invalid email or password</p>}<formonSubmit={handleSubmit(onSubmit)}><inputtype="text"placeholder="email"{...register("email")}/><inputtype="password"placeholder="password"{...register("password")}/><buttontype="submit"disabled={isLoading}>Login</button></form></>);}exportdefaultAuth;

If you want to read more about react-query, here is the official documentation.

Back to top

Sending Verification Emails with Brevo (formerly Sendinblue) SMTP

In this part of the experiment, we will be using Sendinblue to send emails. If you don't have account in 'Sendinblue', you may create your own in this link.

In this part of the experiment, we will be using Sendinblue to send emails. If you don't have an account at 'Sendinblue', you may create one at this link.

Setting up your Sendinblue Account

Once you are done creating your own account, you should see this page: image


Now, click Transactional and then Settings in the left navigation bar. You should be able to see this page: image


Once you are there, click Configuration and Get Your SMTP Key. You should be on this page. Sendinblue


Now, click Generate a new SMTP key. A modal should appear, and you may enter any key you want. In my case, I use lab_exp. Then click the Generate button.

After you click the Generate button, the SMTP key of Sendinblue should appear; copy that and don't lose it.

Setting up an SMTP Server in PocketBase

To set up SMTP in PocketBase, go to the admin UI home page. Then click the setting icon and click on Mail Settings. You should be able to see this page:

image


Now, toggle Use SMTP mail server (recommended). A user interface for configuring your SMTP mail server would appear. Remember the SMTP key you copied earlier? You'll be pasting that in the password field. image


For the username field, go back to Sendinblue and close the modal for the SMTP key. Copy the login: Sendinblue(1)


For the SMTP Server Host field, paste smtp-relay.sendinblue.com, which can be seen back on the Sendinblue page. Then you may now click Save changes.

Send test email will appear, and when you click that, you can test if the configuration you made is working. Sendinblue(2)

Error in Setting up PocketBase Mail Settings

This is an optional part of the documentation in case you encountered an error while setting up the mail settings of PocketBase.

During the experiment at the lab, I encounter this error: pb-error

To resolve this issue, delete the following files:

  • CHANGELOG.md
  • LICENSE.md
  • pocketbase.exe
  • pb_data

Since you deleted the pb_data. You have to repeat the configuration of PocketBase mail settings and create an admin account for the Admin UI. You may read the Dangers of Deleting pb_data included in this documentation.

PocketBase Collection Types

In PocketBase, there are two types of collections:

  • Base Collection - is the default collection type, and you can use it for any type of data.
  • Auth Collection - contains extra fields to manage users, like username, email, and verified. An example of an Auth collection is the PocketBase default user collections.

This was discussed because later we will be creating a simple way of authenticating users.

Source:https://www.makeuseof.com/pocketbase-what-and-how/#:~:text=Creating%20a%20Collection%20in%20PocketBase&text=A%20collection%20can%20either%20be,username%2C%20email%2C%20and%20verified.

Getting the Records

In getting the records into PocketBase, here is an example from the official documentation:

importPocketBasefrom'pocketbase';constpb=newPocketBase('http://127.0.0.1:8090');
...
constrecord1=awaitpb.collection('posts').getOne('RECORD_ID',{expand: 'relField1,relField2.subRelField',});

This is how we implement it in hooks/UseVerified.js:

constuser_id=PB.authStore.model.id;constuser_data=awaitPB.collection("users").getOne(user_id);setIsVerified(user_data.verified);

Sending the Email Verification

To start off, we need the following:

  • First, a button that will send email verification
  • Then, a function will handle the click event for sending email verification.
  • Lastly, an indicator of whether the user is verified or not

Earlier, UseVerified.js was mentioned; add the following function to that file:

asyncfunctionrequestVerification(){constemail=PB.authStore.model.email;constresponse=awaitPB.collection("users").requestVerification(email);if(response){alert("Verification email sent! Check your inbox.");}}

Now, we're done with the function that will handle the sending of emails. In Auth.js, import the UseVerified custom hook:

importUseVerifiedfrom"hooks/UseVerified";
...
const{ is_verified, requestVerification }=UseVerified();

Now, create the indicator and the button in Auth.js:

<p>Verified: {is_verified.toString()}</p>{!is_verified&&<buttononClick={requestVerification}>Send Verification</button>}

Your UseVerified.js should look like the following:

import{useState,useEffect}from"react";importPBfrom"lib/pocketbase";functionUseVerified(){const[is_verified,setIsVerified]=useState(false);useEffect(()=>{asyncfunctioncheckVerified(){constuser_id=PB.authStore.model.id;constuser_data=awaitPB.collection("users").getOne(user_id);console.log("Fetch data: ",user_data);setIsVerified(user_data.verified);}constis_logged_in=PB.authStore.isValid;/* Invoke checkVerified() function if the user is logged in */if(is_logged_in){checkVerified();}},[]);asyncfunctionrequestVerification(){constemail=PB.authStore.model.email;constresponse=awaitPB.collection("users").requestVerification(email);if(response){alert("Verification email sent! Check your inbox.");}}return{ is_verified, requestVerification };}exportdefaultUseVerified;

In testing it, you should use your email account, and you should be able to receive this email once you test it: Verified

Click the Verify button in the email, return to your React app, and refresh it. Verified should have a value of true now.

Back to top

Automatic Re-fetching with the useQuery Hook

In this chapter of the documentation, we will be making changes to the app from the previous chapter and enabling it to reflect the changes that are happening in the database (PocketBase).

In order to do this, we will be using useQuery in React, and you may visit the official documentation by clicking this.

There will be a huge changes in UseVerified.js, first import the useQuery:

There will be huge changes in UseVerified.js. First, import useQuery.

import{useQuery}from"react-query";

Then UseVerified() will have a different return value:

returnuseQuery({queryFn: checkVerified,queryKey: ["check-verified",user_id]});

This is the last chapter in this documentation; you can click this to see the whole change in UseVerified.js. There were minor changes in Auth.js as well; you can click [this](https://github.com/MadrinanComLab/Exp-PocketBase/blob/master/pb_app/src/components/Auth.js to see the changes.

Explaining the Attributes of the useQuery Option

Keys in the option of useQuery was case sensitive. For example, you cannot make queryFn into queryFunction or other keys that you may wish to use.

The object that is used as an argument is called options, and each of its attributes has a different purpose:

  • queryFn - The function you will assign here will be responsible for fetching the data you need.

Note: Since checkVerified was used to be the value of queryFunction, the return value of it will change:

returnuser_data.verified
  • queryKey - This will be used for data caching, and the value of "check-verified" will be the key, and user_id is the second argument, which in this case will be used to query the user by its record id.

Note: The value of user_id will come from the code snippet below. Remember, this will have a value when you log in a user, then it becomes undefined when no user is logged in, and that is why optional chaining was implemented.

...
exportdefaultfunctionUseVerified(){constuser_id=PB.authStore.model?.id;
...
}

Back to top


Did You Like This Experiment?

Stay tuned for upcoming projects and experiments by following me on the following accounts:

About

This repository is an experiment that aims to learn about PocketBase and provide helpful notes to the lab and other developers who want to use PocketBase as a beginner.

Topics

Resources

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - MadrinanComLab/Exp-PocketBase: This repository is an experiment that aims to learn about PocketBase and provide helpful notes to the lab and other developers who want to use PocketBase as a beginner. · GitHub
Skip to content

Latest commit

History

74 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Welcome to PocketBase Experiment!

PocketBase-Experiment

Reference for this experiment:https://www.youtube.com/watch?v=rediFff54JA&t=75s
This experiment was initiated on April 9, 2023.

Table of Contents

Introduction to PocketBase

PocketBase is similar to Firebase (a Google service) and Supabase (an open-source Firebase alternative), which handle backend functions like file upload, saving data to a database, etc.

I know that my introduction to PocketBase was too short to describe it, so here is the link to a video of Fireship where he fully introduces PocketBase.

You can download PocketBase using this link.

Back to top

How to Run PocketBase?

Once you've downloaded PocketBase and it was now in Downloads, do the following:
1. Extract the ZIP file that you downloaded.
2. Rename it PocketBase or however you like, but keep it short.
3. Go to that directory, e.g., Downloads/PocketBase.
4. Now enter this command:

./pocketbase serve

If the command above didn't work for you, try this:

pocketbase serve

Note: The pocketbase you see in the command is not the folder name, it was the keyword for the command.

5. After running the previous command you will see something like this:

2023/04/0922:11:46Serverstartedat http://127.0.0.1:8090REST API: http://127.0.0.1:8090/api/AdminUI: http://127.0.0.1:8090/_/

Back to top

Explaining the Links

Based on the official documentation, the meaning of the links are the following:
http://127.0.0.1:8090 - if pb_public directory exists, serves the static content from it (html, css, images, etc.)
http://127.0.0.1:8090/_/ - Admin dashboard UI
http://127.0.0.1:8090/api/ - REST API

Back to top

Folders in a Pre-Built Pocket Base

The prebuilt PocketBase executable will automatically create and manage 2 new directories alongside the executable:
pb_data - stores your application data, uploaded files, etc. (usually should be added in .gitignore).
pb_migrations - contains JS migration files with your collection changes (can be safely committed in your repository).

Back to top

User-Defined Folder

You have to add the following folder:
pb_public - This is the folder that will be hosted by PocketBase. Here is an example:
1. After creating the pb_public folder, create a file inside this folder named index.html and copy and paste the following code:

<!DOCTYPE html><htmllang="en"><head><metacharset="UTF-8"><metahttp-equiv="X-UA-Compatible" content="IE=edge"><metaname="viewport" content="width=device-width, initial-scale=1.0"><title>Testing123</title></head><body><h1>Welcome to PocketBase Experiment by MadriñanComLab!</h1></body></html>

2. Then go to this URL:

http://127.0.0.1:8090/

Back to top

Adding a React Application to a PocketBase App:

To add the React app, just do the following command:

npm create react-app <app_name>

Example:

npm create react-app pb_app

Back to top

Regaining Access to the PocketBase Admin UI

Just in case you have forgotten your password. What you can do is:
1. Stop the server of PocketBase on your computer (if it is running).
2. Delete the pb_data
3. Run your PocketBase
4. Go to http://127.0.0.1:8090/_/
5. Then you can now set your new email and password.

Back to top

Dangers of Deleting pb_data

Deleting this folder was helpful when you forgot your password for the admin UI, but deleting it will also affect the configuration you set in your PocketBase application.

Later on, configuring the mail settings of PocketBase will be discussed in this documentation.

Additional Note: By deleting the pb_data, you are deleting the data in your database. DO NOT delete pb_migration because this contains the structure of your database. You may delete it unless you intend to delete the structure of the database. But to just regain access to the admin side of your database, just delete the pb_data.

Back to top

Creating a New Collection

The admin UI was straightforward, but in case you are hesitant, here is a brief introduction: Steps-1-to-31. Start with creating your collection. After you click that, a modal will appear on the right.
2. Name your collection however you want. Think of a collection as a table, and you can see in the suggestion that it follows the naming convention of SQL (where tables are named plural).
3. After naming your first collection, you can then create the first field in your collection.

Steps-4-to-54. After clicking the New field button, options for data type would appear.
5. Name your first field here. Think of a field as a column in your database.
Steps-66. If you click the cog icon inline with your field, an option for min and max length would appear as well as the regex.
Steps-7PB-Index-Modal7. If you click "New index," a modal will appear for you to setup your indexes.

Unfamiliar with indexes? You can read about it here

8. You can now save your first collection. Ow, wait, you aren't able to save it? Well, that was because there is already a "users" collection that was initially included in your first PocketBase application.

I apologize about it; this README was created while first exploring PocketBase.

Back to top

React and PocketBase SDK Setup

In the tutorial, the following files were added to the root folder of pb_app:
.env.local
jsconfig.json - In the code here, what it does is make the src folder the root directory for importing and avoid the example imports below.:

importxfrom"../../../folder/file";

Delete the following in pb_app/src:
App.css
App.test.js
index.css
logo.svg
reportWebVitals.js
setupTest.js
Remove the following in index.js:

importreportWebVitalsfrom'./reportWebVitals';import'./index.css';
...
// If you want to start measuring performance in your app, pass a function// to log results (for example: reportWebVitals(console.log))// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitalsreportWebVitals();

Remove the following in App.js:

importlogofrom'./logo.svg';import'./App.css';
...
// This is the chuck of code in return statement<divclassName="App"><headerclassName="App-header"><imgsrc={logo}className="App-logo"alt="logo"/><p>
Edit <code>src/App.js</code> and save to reload.
</p><aclassName="App-link"href="https://reactjs.org"target="_blank"rel="noopener noreferrer">
Learn React
</a></header></div>

You can now add the JavaScript PocketBase SDK by running the following command in your terminal:

npm install pocketbase --save

Note: Make sure you are in the pb_app directory, where the React app is.

Then create the /lib/pocketbase.js file inside the src folder and add the following code snippet:

importPocketBasefrom"pocketbase";constpb=newPocketBase(process.env.REACT_APP_PB_URL);exportdefaultpb;

Note: The value of REACT_APP_PB_URL was http://127.0.0.1:8090, and it was in the .env.local

Now, to test if everything is working, run the following command:

npm start

Back to top

Authentication (with react-hook-form)

PocketBase provides documentation about their authentication; you can read it here if you want to read more about it.

Examples

The following will be a set of different examples of PocketBase authentication.

#1

First, create Auth.js in src. For now, the Auth component will contain the following:

importPBfrom"lib/pocketbase";functionAuth(){return(<>{/* For a simple example of pocketbase authentication */}<h1>Logged In: {PB.authStore.isValid.toString()}</h1></>);}exportdefaultAuth;

Import the Auth component to App.js:

importAuthfrom"Auth";functionApp(){return(<><Auth/></>);}exportdefaultApp;

Your browser should display something like this: ss-3

The first example makes sense that it displays false because a user hasn't logged in yet, right?

#2

Now, let's level up the previous example. Still in Auth.js, do the following:

importPBfrom"lib/pocketbase";import{useState}from"react";import{useForm}from"react-hook-form";functionAuth(){const{ register, handleSubmit }=useForm();const[is_loading,setLoading]=useState(false);asyncfunctionlogin(data){/* This function was a custom function of handleSubmit() */setLoading(true);try{constauth_data=awaitPB.collection("users").authWithPassword(data.email,data.password);}catch(error){console.log(error);}setLoading(false);}return(<><h1>Logged In: {PB.authStore.isValid.toString()}</h1>{is_loading&&<p>Loading...</p>}<formonSubmit={handleSubmit(login)}><inputtype="text"placeholder="email"{...register("email")}/><inputtype="password"placeholder="password"{...register("password")}/><buttontype="submit"disabled={is_loading}>Login</button></form></>);}exportdefaultAuth;

Before we proceed with creating the first user, let's breakdown the example code above because there was a huge change between examples #1 and #2.

  • First is about the React-hook-form, which will help us handle the sample login:
const{ register, handleSubmit }=useForm();

To install react-hook-form in your project, just run the following command:

npm install react-hook-form

Note: Make sure you are in pb_app folder before you install this npm library.

  • State object that will be used for a simple loading indicator.
const[is_loading,setLoading]=useState(false);

  • Then, on the actual code for validation, there was a comment on the code to help break it down:
asyncfunctionlogin(data){/* This function was a custom function of handleSubmit() *//* Set is_loading to true to indicated the request in on process */setLoading(true);/* Try catch statement was used to handle the possible error that may occur. */try{constauth_data=awaitPB/* The 'collection' is the collection of pocketbase */.collection("users")/* 'authWithPassword' will handle the authentication */.authWithPassword(data.email,data.password);}catch(error){console.log(error);}setLoading(false);}

Now, let's create our first user. You can use whatever email and password you want: sample-usersample-user(1)

After this, you can try it to see if it works.

#3

Users can now log in, and for the last example, we will enable users to log out. Do this in Auth.js.:

importPBfrom"lib/pocketbase";import{useState,useEffect}from"react";import{useForm}from"react-hook-form";functionAuth(){const{ register, handleSubmit }=useForm();const[is_loading,setLoading]=useState(false);constis_logged_in=PB.authStore.isValid;/* Later, this will be use to re-render the Auth component */const[dummy,setDummy]=useState(0);asyncfunctionlogin(data){/* This function was a custom function of handleSubmit() */setLoading(true);try{constauth_data=awaitPB.collection("users").authWithPassword(data.email,data.password);}catch(error){console.log(error);}setLoading(false);}functionlogout(){/* logout() function was not an asynchronous function because it doesn't do an API call. Instead, it simply clear the cookie */PB.authStore.clear();setDummy(Math.random());}/* Just another way of conditional rendering in React */if(is_logged_in){return(<>{/* If a user has logged in, display the email address */}<h1>Logged In: {is_logged_in&&PB.authStore.model.email}</h1><buttononClick={logout}>Log Out</button></>);}return(<><h1>Welcome to Login Page!</h1>{is_loading&&<p>Loading...</p>}<formonSubmit={handleSubmit(login)}><inputtype="text"placeholder="email"{...register("email")}/><inputtype="password"placeholder="password"{...register("password")}/><buttontype="submit"disabled={is_loading}>Login</button></form></>);}exportdefaultAuth;

Back to top

Auth Hooks (with react-query)

In this part of the documentation, we will be destructuring the example code from previous chapter.

Destructuring Logout Function

First, we'll destructure the function for logging out. Create a hooks folder in src and create UseLogout.js, which will contain the following code:

importPBfrom"lib/pocketbase";import{useState}from"react";functionUseLogout(){/* Later, this will be use to re-render the Auth component */const[dummy,setDummy]=useState(0);functionlogout(){/* logout() function was not an asynchronous function because it doesn't do an API call. Instead, it simply clear the cookie */PB.authStore.clear();setDummy(Math.random());}returnlogout;}exportdefaultUseLogout;

Note: The code above was an example of React custom hooks.


The Auth.js will look like this:

/* The custom hook was imported to Auth.js */importUseLogoutfrom"hooks/UseLogout";importPBfrom"lib/pocketbase";import{useState,useEffect}from"react";import{useForm}from"react-hook-form";functionAuth(){/* logout() function was declared like this */constlogout=UseLogout();const{ register, handleSubmit }=useForm();const[is_loading,setLoading]=useState(false);constis_logged_in=PB.authStore.isValid;asyncfunctionlogin(data){/* This function was a custom function of handleSubmit() */setLoading(true);try{constauth_data=awaitPB.collection("users").authWithPassword(data.email,data.password);}catch(error){console.log(error);}setLoading(false);}if(is_logged_in){return(<>{/* If a user has logged in, display the email address */}<h1>Logged In: {is_logged_in&&PB.authStore.model.email}</h1><buttononClick={logout}>Log Out</button></>);}return(<><h1>Welcome to Login Page!</h1>{is_loading&&<p>Loading...</p>}<formonSubmit={handleSubmit(login)}><inputtype="text"placeholder="email"{...register("email")}/><inputtype="password"placeholder="password"{...register("password")}/><buttontype="submit"disabled={is_loading}>Login</button></form></>);}exportdefaultAuth;

If you try this, it will work well, but the same problem as in the previous chapter will remain: the credentials you've inputted will remain.

To resolve this, add reset in the destructuring of useForm():

const{ register, handleSubmit, reset }=useForm();

Then, add this reset() function to the login function:

asyncfunctionlogin(data){/* This function was a custom function of handleSubmit() */setLoading(true);try{constauth_data=awaitPB.collection("users").authWithPassword(data.email,data.password);}catch(error){console.log(error);}setLoading(false);/* reset() was added here */reset();}

Destructuring Login Function

Now that we've destructured the logout function using a custom hook, we'll take a few steps to destructure the login as well.

Create UseLogin.js and it will contain the following:

importPBfrom"lib/pocketbase";import{useState}from"react";functionUseLogin(){const[is_loading,setLoading]=useState(false);asyncfunctionlogin({ email, password }){/* This function was a custom function of handleSubmit() */setLoading(true);try{constauth_data=awaitPB.collection("users").authWithPassword(email,password);}catch(error){console.log(error);}setLoading(false);}return{ login, is_loading };}exportdefaultUseLogin;

Since we removed the login function, Auth.js will look like this:

importUseLoginfrom"hooks/UseLogin";importUseLogoutfrom"hooks/UseLogout";importPBfrom"lib/pocketbase";import{useForm}from"react-hook-form";functionAuth(){constlogout=UseLogout();const{ login, is_loading }=UseLogin();const{ register, handleSubmit, reset }=useForm();constis_logged_in=PB.authStore.isValid;asyncfunctiononSubmit(data){login(data);reset();}if(is_logged_in){return(<>{/* If a user has logged in, display the email address */}<h1>Logged In: {is_logged_in&&PB.authStore.model.email}</h1><buttononClick={logout}>Log Out</button></>);}return(<><h1>Welcome to Login Page!</h1>{is_loading&&<p>Loading...</p>}<formonSubmit={handleSubmit(onSubmit)}><inputtype="text"placeholder="email"{...register("email")}/><inputtype="password"placeholder="password"{...register("password")}/><buttontype="submit"disabled={is_loading}>Login</button></form></>);}exportdefaultAuth;

Destructuring API call to PocketBase

In this part of the documentation, we will be using the react-query, and to install this library, run this command:

npm install react-query

Before we proceed to destructuring, we have to do the following on App.js to be able to implement the react-query and do the destructuring:

importAuthfrom"Auth";/* Import the necessary functions */import{QueryClientProvider,QueryClient}from"react-query";/* Initialize the QueryClient */constqueryClient=newQueryClient();functionApp(){return({/* Wrap the Auth component with QueryClientProvider and use queryClient as client */}<QueryClientProviderclient={ queryClient }><Auth/></QueryClientProvider>);}exportdefaultApp;

UseLogin.js will look like this after the destructuring:

importPBfrom"lib/pocketbase";/* useMutation was imported */import{useMutation}from"react-query"/* useState for is_loading was removed because react-query has built-in and it was isLoading*/functionUseLogin(){/* Because login() function was used as argument to useMutation, we no longer need to wrap it with try catch */asyncfunctionlogin({ email, password }){/* This function was a custom function of handleSubmit() */constauth_data=awaitPB.collection("users").authWithPassword(email,password);}/* login() was passed in useMutation as argument */returnuseMutation(login)}exportdefaultUseLogin;

And because of these changes, Auth.js will look like this:

importUseLoginfrom"hooks/UseLogin";importUseLogoutfrom"hooks/UseLogout";importPBfrom"lib/pocketbase";import{useForm}from"react-hook-form";functionAuth(){constlogout=UseLogout();const{/* "mutate" will be used to invoke the login() function */
mutate,/* "is_loading" is now isLoading() which is available to useMutation() by default */
isLoading,/* isError was also available to useMutation() by default */
isError }=UseLogin();const{ register, handleSubmit, reset }=useForm();constis_logged_in=PB.authStore.isValid;asyncfunctiononSubmit(data){mutate(data);reset();}if(is_logged_in){return(<>{/* If a user has logged in, display the email address */}<h1>Logged In: {is_logged_in&&PB.authStore.model.email}</h1><buttononClick={logout}>Log Out</button></>);}return(<><h1>Welcome to Login Page!</h1>{isLoading&&<p>Loading...</p>}{isError&&<p>Invalid email or password</p>}<formonSubmit={handleSubmit(onSubmit)}><inputtype="text"placeholder="email"{...register("email")}/><inputtype="password"placeholder="password"{...register("password")}/><buttontype="submit"disabled={isLoading}>Login</button></form></>);}exportdefaultAuth;

If you want to read more about react-query, here is the official documentation.

Back to top

Sending Verification Emails with Brevo (formerly Sendinblue) SMTP

In this part of the experiment, we will be using Sendinblue to send emails. If you don't have account in 'Sendinblue', you may create your own in this link.

In this part of the experiment, we will be using Sendinblue to send emails. If you don't have an account at 'Sendinblue', you may create one at this link.

Setting up your Sendinblue Account

Once you are done creating your own account, you should see this page: image


Now, click Transactional and then Settings in the left navigation bar. You should be able to see this page: image


Once you are there, click Configuration and Get Your SMTP Key. You should be on this page. Sendinblue


Now, click Generate a new SMTP key. A modal should appear, and you may enter any key you want. In my case, I use lab_exp. Then click the Generate button.

After you click the Generate button, the SMTP key of Sendinblue should appear; copy that and don't lose it.

Setting up an SMTP Server in PocketBase

To set up SMTP in PocketBase, go to the admin UI home page. Then click the setting icon and click on Mail Settings. You should be able to see this page:

image


Now, toggle Use SMTP mail server (recommended). A user interface for configuring your SMTP mail server would appear. Remember the SMTP key you copied earlier? You'll be pasting that in the password field. image


For the username field, go back to Sendinblue and close the modal for the SMTP key. Copy the login: Sendinblue(1)


For the SMTP Server Host field, paste smtp-relay.sendinblue.com, which can be seen back on the Sendinblue page. Then you may now click Save changes.

Send test email will appear, and when you click that, you can test if the configuration you made is working. Sendinblue(2)

Error in Setting up PocketBase Mail Settings

This is an optional part of the documentation in case you encountered an error while setting up the mail settings of PocketBase.

During the experiment at the lab, I encounter this error: pb-error

To resolve this issue, delete the following files:

  • CHANGELOG.md
  • LICENSE.md
  • pocketbase.exe
  • pb_data

Since you deleted the pb_data. You have to repeat the configuration of PocketBase mail settings and create an admin account for the Admin UI. You may read the Dangers of Deleting pb_data included in this documentation.

PocketBase Collection Types

In PocketBase, there are two types of collections:

  • Base Collection - is the default collection type, and you can use it for any type of data.
  • Auth Collection - contains extra fields to manage users, like username, email, and verified. An example of an Auth collection is the PocketBase default user collections.

This was discussed because later we will be creating a simple way of authenticating users.

Source:https://www.makeuseof.com/pocketbase-what-and-how/#:~:text=Creating%20a%20Collection%20in%20PocketBase&text=A%20collection%20can%20either%20be,username%2C%20email%2C%20and%20verified.

Getting the Records

In getting the records into PocketBase, here is an example from the official documentation:

importPocketBasefrom'pocketbase';constpb=newPocketBase('http://127.0.0.1:8090');
...
constrecord1=awaitpb.collection('posts').getOne('RECORD_ID',{expand: 'relField1,relField2.subRelField',});

This is how we implement it in hooks/UseVerified.js:

constuser_id=PB.authStore.model.id;constuser_data=awaitPB.collection("users").getOne(user_id);setIsVerified(user_data.verified);

Sending the Email Verification

To start off, we need the following:

  • First, a button that will send email verification
  • Then, a function will handle the click event for sending email verification.
  • Lastly, an indicator of whether the user is verified or not

Earlier, UseVerified.js was mentioned; add the following function to that file:

asyncfunctionrequestVerification(){constemail=PB.authStore.model.email;constresponse=awaitPB.collection("users").requestVerification(email);if(response){alert("Verification email sent! Check your inbox.");}}

Now, we're done with the function that will handle the sending of emails. In Auth.js, import the UseVerified custom hook:

importUseVerifiedfrom"hooks/UseVerified";
...
const{ is_verified, requestVerification }=UseVerified();

Now, create the indicator and the button in Auth.js:

<p>Verified: {is_verified.toString()}</p>{!is_verified&&<buttononClick={requestVerification}>Send Verification</button>}

Your UseVerified.js should look like the following:

import{useState,useEffect}from"react";importPBfrom"lib/pocketbase";functionUseVerified(){const[is_verified,setIsVerified]=useState(false);useEffect(()=>{asyncfunctioncheckVerified(){constuser_id=PB.authStore.model.id;constuser_data=awaitPB.collection("users").getOne(user_id);console.log("Fetch data: ",user_data);setIsVerified(user_data.verified);}constis_logged_in=PB.authStore.isValid;/* Invoke checkVerified() function if the user is logged in */if(is_logged_in){checkVerified();}},[]);asyncfunctionrequestVerification(){constemail=PB.authStore.model.email;constresponse=awaitPB.collection("users").requestVerification(email);if(response){alert("Verification email sent! Check your inbox.");}}return{ is_verified, requestVerification };}exportdefaultUseVerified;

In testing it, you should use your email account, and you should be able to receive this email once you test it: Verified

Click the Verify button in the email, return to your React app, and refresh it. Verified should have a value of true now.

Back to top

Automatic Re-fetching with the useQuery Hook

In this chapter of the documentation, we will be making changes to the app from the previous chapter and enabling it to reflect the changes that are happening in the database (PocketBase).

In order to do this, we will be using useQuery in React, and you may visit the official documentation by clicking this.

There will be a huge changes in UseVerified.js, first import the useQuery:

There will be huge changes in UseVerified.js. First, import useQuery.

import{useQuery}from"react-query";

Then UseVerified() will have a different return value:

returnuseQuery({queryFn: checkVerified,queryKey: ["check-verified",user_id]});

This is the last chapter in this documentation; you can click this to see the whole change in UseVerified.js. There were minor changes in Auth.js as well; you can click [this](https://github.com/MadrinanComLab/Exp-PocketBase/blob/master/pb_app/src/components/Auth.js to see the changes.

Explaining the Attributes of the useQuery Option

Keys in the option of useQuery was case sensitive. For example, you cannot make queryFn into queryFunction or other keys that you may wish to use.

The object that is used as an argument is called options, and each of its attributes has a different purpose:

  • queryFn - The function you will assign here will be responsible for fetching the data you need.

Note: Since checkVerified was used to be the value of queryFunction, the return value of it will change:

returnuser_data.verified
  • queryKey - This will be used for data caching, and the value of "check-verified" will be the key, and user_id is the second argument, which in this case will be used to query the user by its record id.

Note: The value of user_id will come from the code snippet below. Remember, this will have a value when you log in a user, then it becomes undefined when no user is logged in, and that is why optional chaining was implemented.

...
exportdefaultfunctionUseVerified(){constuser_id=PB.authStore.model?.id;
...
}

Back to top


Did You Like This Experiment?

Stay tuned for upcoming projects and experiments by following me on the following accounts:

About

This repository is an experiment that aims to learn about PocketBase and provide helpful notes to the lab and other developers who want to use PocketBase as a beginner.

Topics

Resources

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); GitHub - MadrinanComLab/Exp-PocketBase: This repository is an experiment that aims to learn about PocketBase and provide helpful notes to the lab and other developers who want to use PocketBase as a beginner. · GitHub
Skip to content

Latest commit

History

74 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Welcome to PocketBase Experiment!

PocketBase-Experiment

Reference for this experiment:https://www.youtube.com/watch?v=rediFff54JA&t=75s
This experiment was initiated on April 9, 2023.

Table of Contents

Introduction to PocketBase

PocketBase is similar to Firebase (a Google service) and Supabase (an open-source Firebase alternative), which handle backend functions like file upload, saving data to a database, etc.

I know that my introduction to PocketBase was too short to describe it, so here is the link to a video of Fireship where he fully introduces PocketBase.

You can download PocketBase using this link.

Back to top

How to Run PocketBase?

Once you've downloaded PocketBase and it was now in Downloads, do the following:
1. Extract the ZIP file that you downloaded.
2. Rename it PocketBase or however you like, but keep it short.
3. Go to that directory, e.g., Downloads/PocketBase.
4. Now enter this command:

./pocketbase serve

If the command above didn't work for you, try this:

pocketbase serve

Note: The pocketbase you see in the command is not the folder name, it was the keyword for the command.

5. After running the previous command you will see something like this:

2023/04/0922:11:46Serverstartedat http://127.0.0.1:8090REST API: http://127.0.0.1:8090/api/AdminUI: http://127.0.0.1:8090/_/

Back to top

Explaining the Links

Based on the official documentation, the meaning of the links are the following:
http://127.0.0.1:8090 - if pb_public directory exists, serves the static content from it (html, css, images, etc.)
http://127.0.0.1:8090/_/ - Admin dashboard UI
http://127.0.0.1:8090/api/ - REST API

Back to top

Folders in a Pre-Built Pocket Base

The prebuilt PocketBase executable will automatically create and manage 2 new directories alongside the executable:
pb_data - stores your application data, uploaded files, etc. (usually should be added in .gitignore).
pb_migrations - contains JS migration files with your collection changes (can be safely committed in your repository).

Back to top

User-Defined Folder

You have to add the following folder:
pb_public - This is the folder that will be hosted by PocketBase. Here is an example:
1. After creating the pb_public folder, create a file inside this folder named index.html and copy and paste the following code:

<!DOCTYPE html><htmllang="en"><head><metacharset="UTF-8"><metahttp-equiv="X-UA-Compatible" content="IE=edge"><metaname="viewport" content="width=device-width, initial-scale=1.0"><title>Testing123</title></head><body><h1>Welcome to PocketBase Experiment by MadriñanComLab!</h1></body></html>

2. Then go to this URL:

http://127.0.0.1:8090/

Back to top

Adding a React Application to a PocketBase App:

To add the React app, just do the following command:

npm create react-app <app_name>

Example:

npm create react-app pb_app

Back to top

Regaining Access to the PocketBase Admin UI

Just in case you have forgotten your password. What you can do is:
1. Stop the server of PocketBase on your computer (if it is running).
2. Delete the pb_data
3. Run your PocketBase
4. Go to http://127.0.0.1:8090/_/
5. Then you can now set your new email and password.

Back to top

Dangers of Deleting pb_data

Deleting this folder was helpful when you forgot your password for the admin UI, but deleting it will also affect the configuration you set in your PocketBase application.

Later on, configuring the mail settings of PocketBase will be discussed in this documentation.

Additional Note: By deleting the pb_data, you are deleting the data in your database. DO NOT delete pb_migration because this contains the structure of your database. You may delete it unless you intend to delete the structure of the database. But to just regain access to the admin side of your database, just delete the pb_data.

Back to top

Creating a New Collection

The admin UI was straightforward, but in case you are hesitant, here is a brief introduction: Steps-1-to-31. Start with creating your collection. After you click that, a modal will appear on the right.
2. Name your collection however you want. Think of a collection as a table, and you can see in the suggestion that it follows the naming convention of SQL (where tables are named plural).
3. After naming your first collection, you can then create the first field in your collection.

Steps-4-to-54. After clicking the New field button, options for data type would appear.
5. Name your first field here. Think of a field as a column in your database.
Steps-66. If you click the cog icon inline with your field, an option for min and max length would appear as well as the regex.
Steps-7PB-Index-Modal7. If you click "New index," a modal will appear for you to setup your indexes.

Unfamiliar with indexes? You can read about it here

8. You can now save your first collection. Ow, wait, you aren't able to save it? Well, that was because there is already a "users" collection that was initially included in your first PocketBase application.

I apologize about it; this README was created while first exploring PocketBase.

Back to top

React and PocketBase SDK Setup

In the tutorial, the following files were added to the root folder of pb_app:
.env.local
jsconfig.json - In the code here, what it does is make the src folder the root directory for importing and avoid the example imports below.:

importxfrom"../../../folder/file";

Delete the following in pb_app/src:
App.css
App.test.js
index.css
logo.svg
reportWebVitals.js
setupTest.js
Remove the following in index.js:

importreportWebVitalsfrom'./reportWebVitals';import'./index.css';
...
// If you want to start measuring performance in your app, pass a function// to log results (for example: reportWebVitals(console.log))// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitalsreportWebVitals();

Remove the following in App.js:

importlogofrom'./logo.svg';import'./App.css';
...
// This is the chuck of code in return statement<divclassName="App"><headerclassName="App-header"><imgsrc={logo}className="App-logo"alt="logo"/><p>
Edit <code>src/App.js</code> and save to reload.
</p><aclassName="App-link"href="https://reactjs.org"target="_blank"rel="noopener noreferrer">
Learn React
</a></header></div>

You can now add the JavaScript PocketBase SDK by running the following command in your terminal:

npm install pocketbase --save

Note: Make sure you are in the pb_app directory, where the React app is.

Then create the /lib/pocketbase.js file inside the src folder and add the following code snippet:

importPocketBasefrom"pocketbase";constpb=newPocketBase(process.env.REACT_APP_PB_URL);exportdefaultpb;

Note: The value of REACT_APP_PB_URL was http://127.0.0.1:8090, and it was in the .env.local

Now, to test if everything is working, run the following command:

npm start

Back to top

Authentication (with react-hook-form)

PocketBase provides documentation about their authentication; you can read it here if you want to read more about it.

Examples

The following will be a set of different examples of PocketBase authentication.

#1

First, create Auth.js in src. For now, the Auth component will contain the following:

importPBfrom"lib/pocketbase";functionAuth(){return(<>{/* For a simple example of pocketbase authentication */}<h1>Logged In: {PB.authStore.isValid.toString()}</h1></>);}exportdefaultAuth;

Import the Auth component to App.js:

importAuthfrom"Auth";functionApp(){return(<><Auth/></>);}exportdefaultApp;

Your browser should display something like this: ss-3

The first example makes sense that it displays false because a user hasn't logged in yet, right?

#2

Now, let's level up the previous example. Still in Auth.js, do the following:

importPBfrom"lib/pocketbase";import{useState}from"react";import{useForm}from"react-hook-form";functionAuth(){const{ register, handleSubmit }=useForm();const[is_loading,setLoading]=useState(false);asyncfunctionlogin(data){/* This function was a custom function of handleSubmit() */setLoading(true);try{constauth_data=awaitPB.collection("users").authWithPassword(data.email,data.password);}catch(error){console.log(error);}setLoading(false);}return(<><h1>Logged In: {PB.authStore.isValid.toString()}</h1>{is_loading&&<p>Loading...</p>}<formonSubmit={handleSubmit(login)}><inputtype="text"placeholder="email"{...register("email")}/><inputtype="password"placeholder="password"{...register("password")}/><buttontype="submit"disabled={is_loading}>Login</button></form></>);}exportdefaultAuth;

Before we proceed with creating the first user, let's breakdown the example code above because there was a huge change between examples #1 and #2.

  • First is about the React-hook-form, which will help us handle the sample login:
const{ register, handleSubmit }=useForm();

To install react-hook-form in your project, just run the following command:

npm install react-hook-form

Note: Make sure you are in pb_app folder before you install this npm library.

  • State object that will be used for a simple loading indicator.
const[is_loading,setLoading]=useState(false);

  • Then, on the actual code for validation, there was a comment on the code to help break it down:
asyncfunctionlogin(data){/* This function was a custom function of handleSubmit() *//* Set is_loading to true to indicated the request in on process */setLoading(true);/* Try catch statement was used to handle the possible error that may occur. */try{constauth_data=awaitPB/* The 'collection' is the collection of pocketbase */.collection("users")/* 'authWithPassword' will handle the authentication */.authWithPassword(data.email,data.password);}catch(error){console.log(error);}setLoading(false);}

Now, let's create our first user. You can use whatever email and password you want: sample-usersample-user(1)

After this, you can try it to see if it works.

#3

Users can now log in, and for the last example, we will enable users to log out. Do this in Auth.js.:

importPBfrom"lib/pocketbase";import{useState,useEffect}from"react";import{useForm}from"react-hook-form";functionAuth(){const{ register, handleSubmit }=useForm();const[is_loading,setLoading]=useState(false);constis_logged_in=PB.authStore.isValid;/* Later, this will be use to re-render the Auth component */const[dummy,setDummy]=useState(0);asyncfunctionlogin(data){/* This function was a custom function of handleSubmit() */setLoading(true);try{constauth_data=awaitPB.collection("users").authWithPassword(data.email,data.password);}catch(error){console.log(error);}setLoading(false);}functionlogout(){/* logout() function was not an asynchronous function because it doesn't do an API call. Instead, it simply clear the cookie */PB.authStore.clear();setDummy(Math.random());}/* Just another way of conditional rendering in React */if(is_logged_in){return(<>{/* If a user has logged in, display the email address */}<h1>Logged In: {is_logged_in&&PB.authStore.model.email}</h1><buttononClick={logout}>Log Out</button></>);}return(<><h1>Welcome to Login Page!</h1>{is_loading&&<p>Loading...</p>}<formonSubmit={handleSubmit(login)}><inputtype="text"placeholder="email"{...register("email")}/><inputtype="password"placeholder="password"{...register("password")}/><buttontype="submit"disabled={is_loading}>Login</button></form></>);}exportdefaultAuth;

Back to top

Auth Hooks (with react-query)

In this part of the documentation, we will be destructuring the example code from previous chapter.

Destructuring Logout Function

First, we'll destructure the function for logging out. Create a hooks folder in src and create UseLogout.js, which will contain the following code:

importPBfrom"lib/pocketbase";import{useState}from"react";functionUseLogout(){/* Later, this will be use to re-render the Auth component */const[dummy,setDummy]=useState(0);functionlogout(){/* logout() function was not an asynchronous function because it doesn't do an API call. Instead, it simply clear the cookie */PB.authStore.clear();setDummy(Math.random());}returnlogout;}exportdefaultUseLogout;

Note: The code above was an example of React custom hooks.


The Auth.js will look like this:

/* The custom hook was imported to Auth.js */importUseLogoutfrom"hooks/UseLogout";importPBfrom"lib/pocketbase";import{useState,useEffect}from"react";import{useForm}from"react-hook-form";functionAuth(){/* logout() function was declared like this */constlogout=UseLogout();const{ register, handleSubmit }=useForm();const[is_loading,setLoading]=useState(false);constis_logged_in=PB.authStore.isValid;asyncfunctionlogin(data){/* This function was a custom function of handleSubmit() */setLoading(true);try{constauth_data=awaitPB.collection("users").authWithPassword(data.email,data.password);}catch(error){console.log(error);}setLoading(false);}if(is_logged_in){return(<>{/* If a user has logged in, display the email address */}<h1>Logged In: {is_logged_in&&PB.authStore.model.email}</h1><buttononClick={logout}>Log Out</button></>);}return(<><h1>Welcome to Login Page!</h1>{is_loading&&<p>Loading...</p>}<formonSubmit={handleSubmit(login)}><inputtype="text"placeholder="email"{...register("email")}/><inputtype="password"placeholder="password"{...register("password")}/><buttontype="submit"disabled={is_loading}>Login</button></form></>);}exportdefaultAuth;

If you try this, it will work well, but the same problem as in the previous chapter will remain: the credentials you've inputted will remain.

To resolve this, add reset in the destructuring of useForm():

const{ register, handleSubmit, reset }=useForm();

Then, add this reset() function to the login function:

asyncfunctionlogin(data){/* This function was a custom function of handleSubmit() */setLoading(true);try{constauth_data=awaitPB.collection("users").authWithPassword(data.email,data.password);}catch(error){console.log(error);}setLoading(false);/* reset() was added here */reset();}

Destructuring Login Function

Now that we've destructured the logout function using a custom hook, we'll take a few steps to destructure the login as well.

Create UseLogin.js and it will contain the following:

importPBfrom"lib/pocketbase";import{useState}from"react";functionUseLogin(){const[is_loading,setLoading]=useState(false);asyncfunctionlogin({ email, password }){/* This function was a custom function of handleSubmit() */setLoading(true);try{constauth_data=awaitPB.collection("users").authWithPassword(email,password);}catch(error){console.log(error);}setLoading(false);}return{ login, is_loading };}exportdefaultUseLogin;

Since we removed the login function, Auth.js will look like this:

importUseLoginfrom"hooks/UseLogin";importUseLogoutfrom"hooks/UseLogout";importPBfrom"lib/pocketbase";import{useForm}from"react-hook-form";functionAuth(){constlogout=UseLogout();const{ login, is_loading }=UseLogin();const{ register, handleSubmit, reset }=useForm();constis_logged_in=PB.authStore.isValid;asyncfunctiononSubmit(data){login(data);reset();}if(is_logged_in){return(<>{/* If a user has logged in, display the email address */}<h1>Logged In: {is_logged_in&&PB.authStore.model.email}</h1><buttononClick={logout}>Log Out</button></>);}return(<><h1>Welcome to Login Page!</h1>{is_loading&&<p>Loading...</p>}<formonSubmit={handleSubmit(onSubmit)}><inputtype="text"placeholder="email"{...register("email")}/><inputtype="password"placeholder="password"{...register("password")}/><buttontype="submit"disabled={is_loading}>Login</button></form></>);}exportdefaultAuth;

Destructuring API call to PocketBase

In this part of the documentation, we will be using the react-query, and to install this library, run this command:

npm install react-query

Before we proceed to destructuring, we have to do the following on App.js to be able to implement the react-query and do the destructuring:

importAuthfrom"Auth";/* Import the necessary functions */import{QueryClientProvider,QueryClient}from"react-query";/* Initialize the QueryClient */constqueryClient=newQueryClient();functionApp(){return({/* Wrap the Auth component with QueryClientProvider and use queryClient as client */}<QueryClientProviderclient={ queryClient }><Auth/></QueryClientProvider>);}exportdefaultApp;

UseLogin.js will look like this after the destructuring:

importPBfrom"lib/pocketbase";/* useMutation was imported */import{useMutation}from"react-query"/* useState for is_loading was removed because react-query has built-in and it was isLoading*/functionUseLogin(){/* Because login() function was used as argument to useMutation, we no longer need to wrap it with try catch */asyncfunctionlogin({ email, password }){/* This function was a custom function of handleSubmit() */constauth_data=awaitPB.collection("users").authWithPassword(email,password);}/* login() was passed in useMutation as argument */returnuseMutation(login)}exportdefaultUseLogin;

And because of these changes, Auth.js will look like this:

importUseLoginfrom"hooks/UseLogin";importUseLogoutfrom"hooks/UseLogout";importPBfrom"lib/pocketbase";import{useForm}from"react-hook-form";functionAuth(){constlogout=UseLogout();const{/* "mutate" will be used to invoke the login() function */
mutate,/* "is_loading" is now isLoading() which is available to useMutation() by default */
isLoading,/* isError was also available to useMutation() by default */
isError }=UseLogin();const{ register, handleSubmit, reset }=useForm();constis_logged_in=PB.authStore.isValid;asyncfunctiononSubmit(data){mutate(data);reset();}if(is_logged_in){return(<>{/* If a user has logged in, display the email address */}<h1>Logged In: {is_logged_in&&PB.authStore.model.email}</h1><buttononClick={logout}>Log Out</button></>);}return(<><h1>Welcome to Login Page!</h1>{isLoading&&<p>Loading...</p>}{isError&&<p>Invalid email or password</p>}<formonSubmit={handleSubmit(onSubmit)}><inputtype="text"placeholder="email"{...register("email")}/><inputtype="password"placeholder="password"{...register("password")}/><buttontype="submit"disabled={isLoading}>Login</button></form></>);}exportdefaultAuth;

If you want to read more about react-query, here is the official documentation.

Back to top

Sending Verification Emails with Brevo (formerly Sendinblue) SMTP

In this part of the experiment, we will be using Sendinblue to send emails. If you don't have account in 'Sendinblue', you may create your own in this link.

In this part of the experiment, we will be using Sendinblue to send emails. If you don't have an account at 'Sendinblue', you may create one at this link.

Setting up your Sendinblue Account

Once you are done creating your own account, you should see this page: image


Now, click Transactional and then Settings in the left navigation bar. You should be able to see this page: image


Once you are there, click Configuration and Get Your SMTP Key. You should be on this page. Sendinblue


Now, click Generate a new SMTP key. A modal should appear, and you may enter any key you want. In my case, I use lab_exp. Then click the Generate button.

After you click the Generate button, the SMTP key of Sendinblue should appear; copy that and don't lose it.

Setting up an SMTP Server in PocketBase

To set up SMTP in PocketBase, go to the admin UI home page. Then click the setting icon and click on Mail Settings. You should be able to see this page:

image


Now, toggle Use SMTP mail server (recommended). A user interface for configuring your SMTP mail server would appear. Remember the SMTP key you copied earlier? You'll be pasting that in the password field. image


For the username field, go back to Sendinblue and close the modal for the SMTP key. Copy the login: Sendinblue(1)


For the SMTP Server Host field, paste smtp-relay.sendinblue.com, which can be seen back on the Sendinblue page. Then you may now click Save changes.

Send test email will appear, and when you click that, you can test if the configuration you made is working. Sendinblue(2)

Error in Setting up PocketBase Mail Settings

This is an optional part of the documentation in case you encountered an error while setting up the mail settings of PocketBase.

During the experiment at the lab, I encounter this error: pb-error

To resolve this issue, delete the following files:

  • CHANGELOG.md
  • LICENSE.md
  • pocketbase.exe
  • pb_data

Since you deleted the pb_data. You have to repeat the configuration of PocketBase mail settings and create an admin account for the Admin UI. You may read the Dangers of Deleting pb_data included in this documentation.

PocketBase Collection Types

In PocketBase, there are two types of collections:

  • Base Collection - is the default collection type, and you can use it for any type of data.
  • Auth Collection - contains extra fields to manage users, like username, email, and verified. An example of an Auth collection is the PocketBase default user collections.

This was discussed because later we will be creating a simple way of authenticating users.

Source:https://www.makeuseof.com/pocketbase-what-and-how/#:~:text=Creating%20a%20Collection%20in%20PocketBase&text=A%20collection%20can%20either%20be,username%2C%20email%2C%20and%20verified.

Getting the Records

In getting the records into PocketBase, here is an example from the official documentation:

importPocketBasefrom'pocketbase';constpb=newPocketBase('http://127.0.0.1:8090');
...
constrecord1=awaitpb.collection('posts').getOne('RECORD_ID',{expand: 'relField1,relField2.subRelField',});

This is how we implement it in hooks/UseVerified.js:

constuser_id=PB.authStore.model.id;constuser_data=awaitPB.collection("users").getOne(user_id);setIsVerified(user_data.verified);

Sending the Email Verification

To start off, we need the following:

  • First, a button that will send email verification
  • Then, a function will handle the click event for sending email verification.
  • Lastly, an indicator of whether the user is verified or not

Earlier, UseVerified.js was mentioned; add the following function to that file:

asyncfunctionrequestVerification(){constemail=PB.authStore.model.email;constresponse=awaitPB.collection("users").requestVerification(email);if(response){alert("Verification email sent! Check your inbox.");}}

Now, we're done with the function that will handle the sending of emails. In Auth.js, import the UseVerified custom hook:

importUseVerifiedfrom"hooks/UseVerified";
...
const{ is_verified, requestVerification }=UseVerified();

Now, create the indicator and the button in Auth.js:

<p>Verified: {is_verified.toString()}</p>{!is_verified&&<buttononClick={requestVerification}>Send Verification</button>}

Your UseVerified.js should look like the following:

import{useState,useEffect}from"react";importPBfrom"lib/pocketbase";functionUseVerified(){const[is_verified,setIsVerified]=useState(false);useEffect(()=>{asyncfunctioncheckVerified(){constuser_id=PB.authStore.model.id;constuser_data=awaitPB.collection("users").getOne(user_id);console.log("Fetch data: ",user_data);setIsVerified(user_data.verified);}constis_logged_in=PB.authStore.isValid;/* Invoke checkVerified() function if the user is logged in */if(is_logged_in){checkVerified();}},[]);asyncfunctionrequestVerification(){constemail=PB.authStore.model.email;constresponse=awaitPB.collection("users").requestVerification(email);if(response){alert("Verification email sent! Check your inbox.");}}return{ is_verified, requestVerification };}exportdefaultUseVerified;

In testing it, you should use your email account, and you should be able to receive this email once you test it: Verified

Click the Verify button in the email, return to your React app, and refresh it. Verified should have a value of true now.

Back to top

Automatic Re-fetching with the useQuery Hook

In this chapter of the documentation, we will be making changes to the app from the previous chapter and enabling it to reflect the changes that are happening in the database (PocketBase).

In order to do this, we will be using useQuery in React, and you may visit the official documentation by clicking this.

There will be a huge changes in UseVerified.js, first import the useQuery:

There will be huge changes in UseVerified.js. First, import useQuery.

import{useQuery}from"react-query";

Then UseVerified() will have a different return value:

returnuseQuery({queryFn: checkVerified,queryKey: ["check-verified",user_id]});

This is the last chapter in this documentation; you can click this to see the whole change in UseVerified.js. There were minor changes in Auth.js as well; you can click [this](https://github.com/MadrinanComLab/Exp-PocketBase/blob/master/pb_app/src/components/Auth.js to see the changes.

Explaining the Attributes of the useQuery Option

Keys in the option of useQuery was case sensitive. For example, you cannot make queryFn into queryFunction or other keys that you may wish to use.

The object that is used as an argument is called options, and each of its attributes has a different purpose:

  • queryFn - The function you will assign here will be responsible for fetching the data you need.

Note: Since checkVerified was used to be the value of queryFunction, the return value of it will change:

returnuser_data.verified
  • queryKey - This will be used for data caching, and the value of "check-verified" will be the key, and user_id is the second argument, which in this case will be used to query the user by its record id.

Note: The value of user_id will come from the code snippet below. Remember, this will have a value when you log in a user, then it becomes undefined when no user is logged in, and that is why optional chaining was implemented.

...
exportdefaultfunctionUseVerified(){constuser_id=PB.authStore.model?.id;
...
}

Back to top


Did You Like This Experiment?

Stay tuned for upcoming projects and experiments by following me on the following accounts:

About

This repository is an experiment that aims to learn about PocketBase and provide helpful notes to the lab and other developers who want to use PocketBase as a beginner.

Topics

Resources

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages