How to use labels in Appwrite to create a subscription system

Obinna Isiwekpeni - Nov 6 '23 - - Dev Community

A subscription system is the process by which the customer pays a recurring price to access a product or service. The two most common categories of subscriptions are as follows.

  • A subscription for unlimited use of a service or collection of services. The usage may be personal, for a family, or a group utilizing the service simultaneously. Examples of this subscription model are Netflix, Apple Music, and Spotify.
  • A subscription for basic usage or limited access, which is mostly free, and a charge is paid to have unlimited access to the resources provided by that service. An example of this model is Medium.

With a subscription system, specific data and images are hidden from users until they subscribe.

Appwrite, a powerful backend-as-a-service (BaaS) platform that provides APIs for building web and mobile applications, offers the capability of easily managing a user’s access to specific resources. This is done using Labels. For example, a user can be assigned a subscriber label when subscribing to a service.

In this article, we will learn how to use Appwrite labels to manage a user’s access to certain resources.

GitHub

Check out the source code here.

Prerequisites

To follow along with this tutorial, the following are required:

Creating a Next.js project

To create a Next.js project, open a terminal and enter the following command:

npx create-next-app appwrite-labels
Enter fullscreen mode Exit fullscreen mode

We will then answer a series of prompts.

Creating a Next.js app

Next, we will go to the project directory and start the development server on localhost:3000 with the commands below.

cd appwrite-labels && npm run dev
Enter fullscreen mode Exit fullscreen mode

Installing Appwrite

As we mentioned above, Appwrite is a BaaS platform that provides APIs for building web and mobile applications.

To use Appwrite in our application, we will install the Appwrite client-side SDK for web applications using the following command:

npm install appwrite
Enter fullscreen mode Exit fullscreen mode

Creating an Appwrite project

To create a new Appwrite project, we will sign in to the Appwrite cloud using our sign in credentials.

Appwrite Cloud sign in page

Next, we will create a new project. We‘ll name our project Car-Gallery-Subscription-Service.

Creating an Appwrite Project

After the creation of the project, we will copy the Project ID and API Endpoint from the Settings tab, as we will need them in the next step.

Appwrite Project credentials

Setting up the Appwrite SDK

We will create a .env.local file in our project root directory for our environment variables. We will add the following entries to our .env.local file.

PROJECT_ID=<APPWRITE_PROJECT_ID> //Your Appwrite Project ID
ENDPOINT=<APPWRITE_ENDPOINT> //Your Appwrite Cloud endpoint
Enter fullscreen mode Exit fullscreen mode

The values of the environment variables were obtained in the previous step when we created our Appwrite project. To access the environment variables within our application, we will modify the next.config.js file. The file should look like this.

/** @type {import('next').NextConfig} */
const nextConfig = {
    env: {
        PROJECT_ID: process.env.PROJECT_ID,
        ENDPOINT: process.env.ENDPOINT
    }
}
module.exports = nextConfig
Enter fullscreen mode Exit fullscreen mode

Next, we will create a file src/appwrite.ts to abstract our Appwrite SDK calls.

import { Account, Client } from "appwrite";

const client = new Client();

client
  .setEndpoint(process.env.ENDPOINT!)
  .setProject(process.env.PROJECT_ID!)

export const account = new Account(client);
Enter fullscreen mode Exit fullscreen mode

The code above imports the Client and Account objects from Appwrite. Then, we create an instance of the Client object. We set our Endpoint, Project ID that we obtained from the previous step into our client instance.

Appwrite Labels

Appwrite Labels are used to categorize users to grant them access to certain resources. In this tutorial, we will restrict some car images from the user until they have the subscriber label.

First, we will use Appwrite Storage to store our images. To create a storage, click on the Storage tab in the Appwrite project.

Creating a Storage

Then, we will click the Create bucket button to create a bucket. We will name our bucket car-images. We will need the Bucket ID in a later step.

Create a Bucket

After creating our bucket, we will go ahead to create files in the bucket. To create a file, we will click the Create file button and then upload our image files.

Creating a file

After uploading all our image files, we will set permissions on our Bucket and files. To do so, we will click on the Settings tab of our Bucket. Then, click the + button in the Permissions section to add a Label.

Creating a Label

We click on Label to create a label. We name our label subscriber so we grant only users that have the subscriber label access to our car images.

Creating a Label

Then, we click on the Add button to add the label.

Next, we update the permissions we want our users to have to our resources. We only want our users to have Read permissions to read the car images.

Updating permissions

We now update our env.local and next.config.js files with the environment variable for our Bucket ID. The env.local file should look like this:

PROJECT_ID=<APPWRITE_PROJECT_ID> //Your Appwrite Project ID
ENDPOINT=<APPWRITE_ENDPOINT> //Your Appwrite Cloud endpoint
BUCKET_ID=<STORAGE_BUCKET_ID> //Your Bucket ID
Enter fullscreen mode Exit fullscreen mode

The next.config.js file should look like this:

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  env: {
    PROJECT_ID: process.env.PROJECT_ID,
    ENDPOINT: process.env.ENDPOINT,
    BUCKET_ID: process.env.BUCKET_ID,
  }
}
module.exports = nextConfig
Enter fullscreen mode Exit fullscreen mode

Building our user interface

Our application has two pages: the home page, which all users can access, and the gallery page, which only subscribers can access.

First, we modify the file src/pages/index.tsx, our home page. The file's content at this time is just a welcome message styled using Tailwind CSS.

export default function Home() {
  return (
    <div className="flex h-full flex-col justify-center items-center">
    <h1 className="text-4xl mb-5 font-bold"> Welcome to the Car Gallery Application</h1>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Next, we create a new file, src/pages/gallery/index.tsx, which is the page displaying the different car images. The file should look like this:

export default function Gallery() {
  return (
    <div className="flex h-full flex-col justify-center items-center">
      <p className="text-2xl mb-5 font-bold"> You are not subscribed</p>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

We create a nav bar to navigate between the two pages. To do this, we modify the src/components/Layout.tsx file.

import Link from 'next/link';
import { usePathname } from 'next/navigation';

const menuItems = [
  {
    href: '/',
    title: 'Home',
  },
  {
    href: '/gallery',
    title: 'Gallery',
  }
];

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  const path = usePathname();
  return (
    <div className='min-h-screen flex flex-col'>
      <div className='flex flex-col md:flex-row flex-1'>
        <aside className="bg-gray-300 w-full md:w-60">
          <nav>
            <ul>
              {menuItems.map(({ href, title }) => (
                <li className='m-2' key={title}>
                  <Link href={href} className={'flex p-2 bg-fuchsia-50 rounded hover:bg-fuchsia-200 cursor-pointer  
                    ${path === href && 'bg-fuchsia-300 text-white'}'}>
                    {title}
                  </Link>
                </li>
              ))}
            </ul>
          </nav>
        </aside>
        <main className="flex-1">{children}</main>
      </div>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Next, we modify the src/pages/_app.tsx to apply the layout to all pages in the application.

import Layout from '@/components/Layout'
import '@/styles/globals.css'
import type { AppProps } from 'next/app'

export default function App({ Component, pageProps }: AppProps) {
  return (
    <Layout>
      <Component {...pageProps} />
    </Layout>
  )
}
Enter fullscreen mode Exit fullscreen mode

At this point, our UI looks like this:

The user interface

We update the src/appwrite.ts file. We import the Storage object from Appwrite to access the files in our bucket. The file should look like this:

import { Account, Client, Storage } from "appwrite";
const client = new Client();

client
  .setEndpoint(process.env.ENDPOINT!)
  .setProject(process.env.PROJECT_ID!)

export const storage = new Storage(client);
export const account = new Account(client);
Enter fullscreen mode Exit fullscreen mode

We update the src/pages/gallery/index.tsx file. The file should look like this at this point:

import { account, storage } from "@/appwrite";
import { AppwriteException, Models } from "appwrite";
import React, { useEffect, useState } from "react";

export default function Gallery() {
  const [imageFiles, setImageFiles] = useState<Models.File[]>([]);
  const [errorCode, setErrorCode] = useState<number>();

  useEffect(() => {
    (async () => {
      try {
        await account.get();
      }
      catch (e) {
        await account.createAnonymousSession();
      }
    })()
  }, [])

  useEffect(() => {
    const getCarImages = async () => {
      try {
        const fileList = await storage.listFiles(process.env.BUCKET_ID!);;
        setImageFiles(fileList.files);
      }
      catch (error: any) {
        if (error instanceof AppwriteException) {
          setErrorCode(error.code);
        }
        console.log(error);
      }
    }
    getCarImages();
  }, [])

  const images = imageFiles?.map(x => {
    const imagePreview = storage.getFilePreview(x.bucketId, x.$id);
    return (
      <li key={imagePreview.href} className="mt-4"><img src={imagePreview.href} width="256" className="h-40" /></li>
    )
  });

  return (
    <>
      {images &&
        <ul className="flex flex-wrap">{images}</ul>
      }
      {errorCode === 401 &&
        (<div className="flex h-full flex-col justify-center items-center">
          <h1 className="text-4xl mb-5 font-bold"> You are not subscribed</h1>
        </div>
        )}
    </>
  )
}
Enter fullscreen mode Exit fullscreen mode

The code above does the following:

  • Lines 6 - 7: We initialize the imageFiles and errorCode states of our component using React’s useState hook.
  • Lines 9 - 18: We use the useEffect hook to get the active Appwrite user session and if there is none, we create an anonymous session.
  • Lines 20-33: We use the useEffect hook to fetch the image files in our Appwrite bucket and set the imageFiles state to the response. If we have an error, we check if the error is an instance of AppwriteException and set the errorCode state to the error code of the response.
  • Lines 36-41: We iterate through our imageFiles and use the getFilePreview method of the Storage object to preview our image files. We append each file as a list item to be displayed on the screen.
  • Lines 43-54: We return the images as an unordered list or a text saying the user is not subscribed if the error code is 401 i.e. the user is not authorized to access the resources.

Now, we run our application and click on the Gallery menu. We see the text You are not subscribed as shown below.

Not subscribed

This is because we specified that only a user who has the label subscriber can access our cars images and our current user does not have that.

To assign the user a label, we click on the Auth menu in our Appwrite project and then click on the particular user we want to assign the label.

Assigning a label

When we click on the particular user, on the Labels section, we assign the user the subscriber label to access our resource and then click the Update button.

Assigning user a label

When we return to our application, the user should be able to view the car images now.

Car Images

For further development, the labels can be assigned through a different subscription workflow which, in turn, assigns the correct permissions to a user.

Conclusion

In this tutorial, we used Appwrite Labels with a Next.js application to restrict user access to images until they are assigned the label that can access the resource, in this case subscriber label. Appwrite Labels are used to create a subscription system and can be extended to other use cases where we want to limit access to specific resources in an application.

Resources

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .