DEV Community

Cover image for Authentication using Appwrite in React - Part 2
Vinit Gupta
Vinit Gupta

Posted on

Authentication using Appwrite in React - Part 2

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 file AppwriteService.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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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 };
Enter fullscreen mode Exit fullscreen mode

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>
);
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

With some CSS, the page would look like this :

Signup Page

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);
        })
    }

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Login Page

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 πŸ˜‰πŸ˜‰.

Oldest comments (6)

Collapse
 
thesouvikmondal profile image
thesouvikmondal

great explanation

Collapse
 
thevinitgupta profile image
Vinit Gupta

Thanks a lotπŸ™Œ

Collapse
 
celeron profile image
Khushal Bhardwaj

Thanks a lot, that worked!

Collapse
 
thevinitgupta profile image
Vinit Gupta

I am glad it helpedπŸ™Œ
You can also checkout the whole series

Collapse
 
celeron profile image
Khushal Bhardwaj

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

Thread Thread
 
thevinitgupta profile image
Vinit Gupta

Sure I will try to explain in my next blog.