Authenticating users is always a good thing.
But creating our own authentication rules and functions and implementing them is a lot effort.
But why go through all the trouble when Appwrite is just around the corner?
In my previous post, I talked all about the setup process. But now it's time for the actual stuff!
So start your Favorite IDE (mine's VS Code), sip your coffee and let's get to work.
Step 1 : Setup Appwrite Service
To start using Appwrite functions, we need to initialize the SDK.
And to do so, you'll need your projectId
and endpoint
(otherwise, Appwrite doesn't know who you are).
Since, we are using a .env
file to store these, we will have to import them.
Note : Create a
services
folder and create a fileAppwriteService.js
file here.
Add the following code :
import { Appwrite } from "appwrite";
const config = {
projectId : process.env.REACT_APP_APPWRITE_PROJECT,
endpoint : process.env.REACT_APP_APPWRITE_ENDPOINT,
};
const appwrite = new Appwrite();
class AppwriteService {
constructor() {
appwrite.setEndpoint(config.endpoint).setProject(config.projectId);
}
}
export default AppwriteService;
As you can see, we are adding the endpoint and projectId for our project.
Now that we have an identity that Appwrite can recognize, we can proceed.
Step 2 : Implementing Authentication APIs
When we talk about authenticating users, there are 3 steps involved :
- Creating a User
- Logging In a User
- Logging out a User
Namely, Signup, Login and Logout.
- Instantiate an account property responsible for handling Auth API calls
- define authentication methods for signup, login, and logout.
Update the Appwrite service file with this code.
import { Appwrite } from "appwrite";
const config = {
projectId : process.env.REACT_APP_APPWRITE_PROJECT,
endpoint : process.env.REACT_APP_APPWRITE_ENDPOINT,
bucketId : process.env.REACT_APP_APPWRITE_BUCKET
};
const appwrite = new Appwrite();
class AppwriteService {
constructor() {
appwrite.setEndpoint(config.endpoint).setProject(config.projectId);
this.account = appwrite.account;
}
createAccount = (email, name, password) =>{
return this.account.create("unique()",email, password,name)
}
loginUser = (email, password) =>{
return this.account.createSession(email,password);
}
logoutUser = () =>{
return this.account.deleteSession('current');
}
}
export default AppwriteService;
You can check out more about the functions above here ๐๐ Accounts API
One thing to notice here is the unique()
string passed in the account.create()
function above. This is used to specify that :
You can use Appwrite to auto-generate a Unique
USER_ID
for each new user. You can also pass in your own UniqueId. But then again, why do something that has already been taken care of ?
Step 3 : Creating Different Components
Now that we have our functions ready, the next step would be to use them.
Create components for Signup, Login and a Navbar that has the option to Logout. You can create these in your own designs.
The main task is to provide routing for different pages. We will use React router for this. So the first step would be install it in your project.
npm install react-router-dom@6
Then we'll have to specify the routes as follows :
import {Routes , Route } from 'react-router-dom';
import './App.css';
import Home from './Components/Home';
import Login from './Components/Login';
import Signup from './Components/Signup';
import Navbar from './Components/Navbar';
function App() {
return (
<div className="App">
<Routes>
<Route exact path={'/'} element={<><Navbar/>
<Home/></>} />
<ImageFilter/></>} />
<Route exact path={'/signup'} element={<Signup/>}/>
<Route exact path={'/login'} element={<Login/>}/>
</Routes>
</div>
);
}
export default App;
Now that we are settled with the routes, we can move on to the actual implementation.
Step 4 : Provide Appwrite In React
To use the Authentication methods, we need to have access to the Appwrite Service Class.
A "simple" approach would be to create an Appwrite instance in every React component that needs to use Appwrite. However, this is a bad approach, for two reasons:
- It would be difficult to test our components
- It is more error prone. We will end up with multiple instances. Our Appwrite service class should only be initialized once (Simpleton Pattern)
A better approach would be to use React's Context API
to provide an Appwrite instance once at the top level of our component hierarchy. Create a new file src/context/Appwrite/index.js in your React project and add the following:
import React from 'react';
const AppwriteContext = React.createContext(null);
export default AppwriteContext;
We will then create a well-encapsulated Appwrite module by defining a new file src/components/Appwrite/index.js which exports the AppwriteService
class and AppwriteContext
.
import AppwriteContext from '../../context/Appwrite';
import Appwrite from '../../services/AppwriteService';
export default Appwrite;
export { AppwriteContext };
The React.createContext() method in src/context/Appwrite/index.js creates two components, AppwriteContext.Provider which is used to provide an Appwrite instance once at the top of our component tree and AppwriteContext.Consumer for every component which requires access to Appwrite.
We will use the AppwriteContext.Provider component to provide an Appwrite instance to the entire application by wrapping it around our root component in /src/index.js, like this:
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { BrowserRouter } from 'react-router-dom';
import Appwrite, {AppwriteContext} from './Components/Appwrite';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<BrowserRouter>
<AppwriteContext.Provider value={new Appwrite()}>
<App />
</AppwriteContext.Provider>
</BrowserRouter>
);
Here, Appwrite is instantiated once and injected into our component tree via React Context API. Now every component which requires access to Appwrite. We can do this using the useContext
API provided by React. The code looks something like this :
import React, {useContext} from 'react';
import {AppwriteContext} from './components/Appwrite';
const SomeExampleComponent = () => {
const appwrite = useContext(AppwriteContext);
return (
<div>This component has access to Appwrite.</div>
);
}
export default SomeExampleComponent;
Step 5 : Signup and Login
Now that we know how to use the Appwrite context, it's time to actually use it.
For the Signup we need 3 things : Name, Email and Password.
We'll use a form and states to get hold of the data.
A simple Signup form along with states and onChange
event listeners, we are ready to use the Appwrite createAccount
method. Since, it returns a promise, we need to use a then and catch block.
But for this to work, we have to import the Appwrite context and consume
it. As discussed above, we implement it using the useContext
API.
The code would look something like this :
import React, { useContext, useState } from 'react'
import SignupImg from "../Assets/Signup.png"
import "../Css/Signup.css"
import Pig from "../Assets/pig.png"
import { useNavigate } from 'react-router-dom';
import { AppwriteContext } from './Appwrite';
function Signup() {
const [name,setName] = useState("");
const [email,setEmail] = useState("");
const [password,setPassword] = useState("");
const navigator = useNavigate();
const appwrite = useContext(AppwriteContext);
const handleSignup = (e) => {
e.preventDefault();
if(name === '' || email==='' || password === ''){
alert('All fields are required');
return;
}
appwrite.createAccount(email, name, password).then((res) =>{
console.log('Success', res);
window.location="/"
}).catch((error) =>{
console.log('Error', error);
})
}
return (
<div className='Signup'>
<div className='Signup-left'>
<div className='signup-home'>
<img className='signup-home-btn' onClick={()=>{
navigator("/");
}} src={Pig} alt="pigshell"/>
</div>
<div className='Signup-head'>
<div>Sign Up</div>
<div className='Signup-subhead'>
Create account to access images from anywhere
</div>
</div>
<div className='Signup-card'>
<form className='Signup-form'>
{/* <label for="name">Your Name</label> */}
<input className='Signup-input' name='name' placeholder='Name' id='signup-name' autoComplete='off' value={name} onChange={(e)=>{
setName(e.target.value);
}}/>
<input className='Signup-input' placeholder='Email' autoComplete='off' id='signup-email' value={email} onChange={(e)=>{
setEmail(e.target.value);
}}/>
<input className='Signup-input' placeholder='Password' type='password' autoComplete='off' id='signup-password' value={password} onChange={(e)=>{
setPassword(e.target.value);
}}/>
<button type="submit" id='signup-btn'
onClick={handleSignup}>Create Account</button>
</form>
</div>
<div className='Signup-footer'>
<p>Already have account? <a className='login-redirect highlight-text' href='/login'>Login Now</a></p>
</div>
</div>
<div className='Signup-right'>
<div className='Signup-welcome'>
<h2>Welcome to PigShell</h2>
<p>Start your journey full of Piggy Awesomeness!</p>
</div>
<div className='Signup-img'>
<img src={SignupImg} alt='signup'/>
</div>
</div>
</div>
)
}
export default Signup
With some CSS, the page would look like this :
Do let me know, if you like the design!!
The code for using the Appwrite create account method is as below :
const handleSignup = (e) => {
e.preventDefault();
if(name === '' || email==='' || password === ''){
alert('All fields are required');
return;
}
appwrite.createAccount(email, name, password).then((res) =>{
console.log('Success', res);
window.location="/"
}).catch((error) =>{
console.log('Error', error);
})
}
Similarly, we can implement the Login function.
The component code it something like this :
import React, { useContext, useState } from 'react'
import LoginImg from "../Assets/Signup.png"
import "../Css/Login.css"
import Pig from "../Assets/pig.png"
import { useNavigate } from 'react-router-dom';
import { AppwriteContext } from './Appwrite';
function Login() {
const [email,setEmail] = useState("");
const [password,setPassword] = useState("");
const navigator = useNavigate();
const appwrite = useContext(AppwriteContext);
const handleLogin = (e) => {
e.preventDefault();
appwrite.loginUser(email,password).then((res) =>{
console.log("Logged In!", res);
window.location = "/"
}).catch((error) =>{
console.log("Error logging in", error);
})
// console.log({ email : email, password : password});
}
return (
<div className='Login'>
<div className='Login-left'>
<div className='login-home'>
<img className='login-home-btn' onClick={()=>{
navigator("/");
}} src={Pig} alt="pigshell"/>
</div>
<div className='Login-head'>
<div>Log In</div>
<div className='Login-subhead'>
Login to view your images
</div>
</div>
<div className='Login-card'>
<form className='Login-form'>
{/* <label for="name">Your Name</label> */}
<input className='Login-input' placeholder='Email' autoComplete='off' id='login-email' value={email} onChange={(e)=>{
setEmail(e.target.value);
}}/>
<input className='Login-input' placeholder='Password' type='password' autoComplete='off' id='login-password' value={password} onChange={(e)=>{
setPassword(e.target.value);
}}/>
<button type="submit" onClick={handleLogin} id='login-btn'>Log In</button>
</form>
</div>
<div className='Login-footer'>
<p>Don't have account? <a className='login-redirect highlight-text' href='/signup'>Signup Here</a></p>
</div>
</div>
<div className='Login-right'>
<div className='Login-welcome'>
<h2>Welcome Back</h2>
<p>We were missing your Piggy Awesomeness!</p>
</div>
<div className='Login-img'>
<img src={LoginImg} alt='login'/>
</div>
</div>
</div>
)
}
export default Login
Check out the whole code here : Pigshell ๐ท
Now that we are ready with the Login and Signup functionalities, we can implement the other parts of the Application that we are developing. Like an Image filter, that converts ordinary images into Pixel images.
Will be available to use really soon ๐๐.
Top comments (6)
Thanks a lot, that worked!
I am glad it helped๐
You can also checkout the whole series
Could you also please explain how to manage user sessions, since the current login creates a new session everytime the user logs in, and that isn't managable
Sure I will try to explain in my next blog.
great explanation
Thanks a lot๐