Building a Todo App with Next.js 13, Clerk, and Supabase.
Tags: supabase, clerk, authentication, jamstack
Published: 2023-07-19 16:41:02

---
title:Building a Todo App with Next.js 13, Clerk, and Supabase.
published: true
description: Learn to build a sleek Todo app with Next.js 13, Clerk for Authentication, and Supabase.
tags: #Supabase #Clerk #Authentication #Jamstack
# cover_image:https://res.cloudinary.com/hackit-africa/image/upload/v1689784690/cleksup.png
# Use a ratio of 100:42 for best results.
# published_at: 2023-07-19 16:28 +0000
---

Introduction

In this guide, we will embark on a journey to build a sleek and powerful Todo application using the latest technology stack, [Next.js 13](https://nextjs.org), [Clerk](https://clerk.com) for Authentication, and [Supabase](https://supabase.com) as the database. With the seamless integration of Clerk and Supabase into Next.js applications, we'll discover how this combination forms a robust foundation for modern web applications, providing security, scalability, and ease of development.

Throughout this blog post, we'll delve into the step-by-step process of setting up Clerk authentication in a Next.js 13 application, ensuring secure access to our Todo app. Then, we'll explore the seamless integration of Supabase, witnessing how it effortlessly handles data storage and retrieval while providing real-time updates for a smoother user experience.

This is a continuation of the blogpost : [Simplifying Authentication in Next.js Applications with Clerk](https://dev.to/musebe/simplifying-authentication-in-nextjs-applications-with-clerk-3adh) hence we shall be focusing on intergrating clerk with Supabase

Let's dive in and discover the magic of building a minimalistic Todo app with Next.js 13, Clerk, and the Supabase database!

## Prerequisites

Before building the Todo app, make sure you have:

1.  Basic web development skills (HTML, CSS, JavaScript).
2.  Familiarity with Next.js basics.
3.  Node.js and npm installed.
4.  A code editor.
5.  Optional: Git for version control.
6.  A [Supabase](https://supabase.com) account for the database.
7.  A [Clerk](https://clerk.com) account for authentication.

With these prerequisites, you're all set to start creating your Todo app using Next.js 13, Clerk, and Supabase. Let's begin!

## Github Repository

Find the starting code on the main branch : [https://github.com/musebe/Nextjs-Clerk](https://github.com/musebe/Nextjs-Clerk). For the final codebase, check out the supabase branch [https://github.com/musebe/Nextjs-Clerk/tree/supabase](https://github.com/musebe/Nextjs-Clerk/tree/supabase). Happy coding!



## Getting Started

To get started clone the main branch from the Github repository [Simplifying Authentication in Next.js Applications with Clerk](https://github.com/musebe/Nextjs-Clerk). The will act as the starting point for our application as its already setup with Authentication.

To clone the applicarion run the following command :

```
git clone https://github.com/musebe/Nextjs-Clerk.git
``` 
Navigate to the project directory and run the following command to install all the project dependencies :

```
npm install
```

To start the application run the command :

```
npm run dev
```

## Supabase Setup 

[Supabase](https://supabase.com/) is an open-source Backend-as-a-Service (BaaS) platform that aims to simplify the development of web and mobile applications. It provides developers with a suite of tools and services to quickly build and scale applications without having to worry about server management or infrastructure setup.

One of the key features of Supabase is real-time updates. It leverages PostgreSQL's capabilities to provide instant and synchronized data updates to clients, making it ideal for applications that require real-time collaboration or live data streams.

To get started, follow the following steps

### Step 1: Sign Up for a Supabase Account

To begin your journey with Supabase, head over to [supabase.com](supabase.com) and click on the `Sign Up` button located in the top right corner of the page. You can choose to sign up using your `GitHub` or `Google` account for a quick registration process. Alternatively, provide your email address and fill in the necessary details to create an account.

### Step 2: Create a New Project

Once you have successfully signed up and logged in, you will be directed to your Supabase dashboard. Here, click on the `New Project` button to create a new project. Give your project a meaningful name and select the preferred region for data storage. Supabase offers multiple regions to ensure low-latency access to your data as highlighted below : 

![Supabase setup](https://res.cloudinary.com/hackit-africa/image/upload/v1689763645/project-setup.png)

### Step 3: Set Up Your Database

After creating your project, you will land on the project overview page. From the left sidebar, select the `SQL Editor` tab. To set up a new database, click on "Create table" template and run the following query.

```
CREATE TABLE todos (
  id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
  user_id VARCHAR,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, NOW()) NOT NULL,
  completed_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, NOW()) NOT NULL,
  todo VARCHAR,
  tag VARCHAR
);

```
![Todos Query](https://res.cloudinary.com/hackit-africa/image/upload/v1689765763/create-todos-query.png)

The Supabase query above creates a table named "todos" with the following columns:

1.  `id` (bigint): This column is an auto-generated primary key of type `bigint`. It will automatically assign a unique identifier to each row added to the table.
    
2.  `user_id` (varchar): This column stores the user identifier associated with the todo. It is of type `varchar`, which means it can hold alphanumeric characters and symbols.
    
3.  `created_at` (timestamp with time zone): This column represents the timestamp when the todo was created. It has a default value set to the current time in Coordinated Universal Time (UTC). The `now()` function retrieves the current timestamp, and the `timezone('utc'::text, ...)` function ensures it is in UTC format.
    
4.  `completed_at` (timestamp with time zone): This column represents the timestamp when the todo was marked as completed. Similar to `created_at`, it has a default value set to the current time in UTC.
    
5.  `todo` (varchar): This column stores the actual todo item, such as a task or reminder. It is of type `varchar`.
    
6.  `tag` (varchar): This column allows categorizing or labeling the todo item with a tag. It is also of type `varchar`.


After running the query above click on the `table editor` tab on the left menu and you will see a `todos` table created with the structure below : 

![Todos Table](https://res.cloudinary.com/hackit-africa/image/upload/v1689765861/todos_table.png)

### Configure RLS (Row Level Security)  for the`todos` table

Configuring Row Level Security (RLS) in Supabase provides an essential layer of data protection and access control for your database tables. RLS allows you to define rules that determine which rows of data a user can access based on their specific attributes or roles. This feature plays a crucial role in enhancing the security and privacy of your application's data.

To create our first row level security run the following command on the `SQL Editor`

```
create or replace function requesting_user_id() returns text as $$
  select nullif(current_setting('request.jwt.claims', true)::json->>'sub', '')::text;
$$ language sql stable;

```

![policy_query](https://res.cloudinary.com/hackit-africa/image/upload/v1689766492/rls_command.png)

The above is a function named `requesting_user_id()`. This function returns the user ID (sub claim) extracted from the JSON web token (JWT) included in the request header. 

Next we need to create a policy that allows only authenticated users to update their own todos. Run the following query as highlighted below : 

```
CREATE POLICY "Authenticated users can update their own todos"
ON public.todos FOR UPDATE
USING (
    auth.role() = 'authenticated'::text
)
WITH CHECK (
    requesting_user_id() = user_id
);

```
![Authenticate](https://res.cloudinary.com/hackit-africa/image/upload/v1689767855/auth_users.png)

This code creates a policy named "Authenticated users can update their own todos" on the `public.todos` table. The policy allows updates to the table only if the user has the role "authenticated" and the requesting user ID matches the `user_id` column in the table.

To check if the above policies have been applied to our `todos` database, navigate to the `Authentication` tab and click the `policies` button. you should be able to see this : 

![Policies](https://res.cloudinary.com/hackit-africa/image/upload/v1689766498/RLS.png)



To finalize the database setup with Supabase, you will need to obtain the Supabase API Key and URL. These credentials are essential for connecting your application to the Supabase database and making authenticated requests to access data or perform CRUD operations.

These credentials serve as your project's authentication tokens. They allow you to interact with the Supabase API securely.

-   API Key: The API Key acts as your secret key to authenticate requests to the Supabase API. It is a long string of characters and numbers, used to identify and authorize your application.
    
-   URL: The URL is the endpoint through which you will make requests to the Supabase API. It typically follows the format `https://your-project-id.supabase.co`, where `your-project-id` is replaced with your specific Supabase project ID.

To view your keys click on the `Settings` tab. This will take you to the project settings where all the keys are stored. Click on the `API`  tab to revel your keys as shown below : 

![Keys](https://res.cloudinary.com/hackit-africa/image/upload/v1689769134/Screenshot_2023-07-19_at_15.18.08.png)

Copy the keys and paste them in the `.env` file of your project as shown below : 

```
NEXT_PUBLIC_SUPABASE_URL= YOUR_SUPABASE_URL
NEXT_PUBLIC_SUPABASE_KEY=YOUR_SUPABASE_ANON_PUBLIC_KEY
```

Supabase requires JWTs be signed with the HS256 signing algorithm and use their signing key. Find the JWT secret key in your Supabase project under **Settings** > **API** in the **Config** section. Copy the `JWT secret` 

![jwt token](https://res.cloudinary.com/hackit-africa/image/upload/v1689766535/JWT_Config.png)

To complete the integration between Supabase and the Clerk authentication service, follow these steps:

1.  Access your Clerk dashboard: Log in to your Clerk account and navigate to the dashboard.
    
2.  Go to JWT Templates: In the Clerk dashboard, locate the **JWT Templates** page and click on it. This page allows you to configure JSON Web Token (JWT) templates for different authentication providers.
    
3.  Create a new template: On the **JWT Templates** page, click the button to create a new template. Select "Supabase" as the authentication provider for which you want to configure the template.
    
4.  Set the Signing Key: In the template configuration, find the field labeled "Signing Key" and paste the "JWT secret" key that you previously copied from your Supabase project. This key is essential for securely signing and verifying the JWTs issued by Clerk.
    

By following these steps and configuring the JWT template in the Clerk dashboard, you have successfully integrated Supabase with Clerk's authentication service. Clerk will now be able to generate and validate JWTs using the Supabase signing key, providing secure and seamless authentication for your application as highlighted below :


![JWT](https://res.cloudinary.com/hackit-africa/image/upload/v1689769434/Screenshot_2023-07-18_at_00.18.43.png)


## Installing Supabase 

Congratulations! With the database set up and Clerk configured with Supabase, you are now ready to integrate Supabase into your Next.js project. To do so, follow these simple steps:

1.  Open your Next.js project: Navigate to the root directory of your Next.js project in your terminal or code editor.
    
2.  Install the Supabase client library: Run the following command to install the official Supabase client library for JavaScript:

```
npm install @supabase/supabase-js
```
This command will fetch and install the required `@supabase/supabase-js` package from the npm registry and add it to your project's dependencies.


## Centralizing Supabase Client

To enhance organization and reusability in your Next.js project, we can centralize the creation and configuration of the Supabase client by creating a `utils` folder at the root of the project structure. Within this folder, we'll add a file named `supabaseClient.js`. This approach helps maintain clean code and facilitates easier management of the Supabase client across multiple components and pages.


Here's how you can achieve it:

Step 1: Create the `utils` folder

In your Next.js project, navigate to the root directory and create a new folder named `utils`.

Step 2: Add `supabaseClient.js` to the `utils` folder

Within the `utils` folder, create a new file named `supabaseClient.js`.

Step 3: Place the Supabase Client Configuration Code

In the `supabaseClient.js` file, add the following code to create and configure the Supabase client:

```
import { createClient } from '@supabase/supabase-js';

export const supabaseClient = async (supabaseToken) => {
  const supabase = createClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL,
    process.env.NEXT_PUBLIC_SUPABASE_KEY,
    {
      global: { headers: { Authorization: `Bearer ${supabaseToken}` } },
    }
  );

  return supabase;
};

```
The purpose of this code snippet is to create a Supabase client, which is a client-side library responsible for communication between the frontend application and the Supabase backend. This client enables seamless interaction with the database, making it easier to perform various operations such as querying data, managing real-time updates, and handling authentication.

The `supabaseClient` function, which is exported from this code snippet, accepts a `supabaseToken` as an input parameter. This token is typically an authentication token obtained from Supabase, uniquely associated with a user.

Inside the function, the `createClient` function from the `@supabase/supabase-js` library is utilized to instantiate the Supabase client. This function requires three arguments: `process.env.NEXT_PUBLIC_SUPABASE_URL`, `process.env.NEXT_PUBLIC_SUPABASE_KEY`, and an object containing additional configuration options.

The first argument, `process.env.NEXT_PUBLIC_SUPABASE_URL`, represents the URL of the Supabase project. It is usually stored as an environment variable and serves as the endpoint for server communication.

The second argument, `process.env.NEXT_PUBLIC_SUPABASE_KEY`, represents the public API key of the Supabase project. Like the URL, this is stored as an environment variable and provides the necessary authentication and authorization for the client.

The third argument is an object that specifies further configuration options for the Supabase client. In this code snippet, it sets the `Authorization` header for each request using the provided `supabaseToken`. This ensures that the client is authenticated and authorized to access the relevant resources on the Supabase backend.

Finally, the `supabaseClient` function returns the configured Supabase client instance, which can then be used across the application to interact with the database and handle real-time updates. By encapsulating the client creation and configuration within this utility, it promotes code reusability and maintains a clean structure within the project.


## **Performing Requests to Fetch and Post Todos from Supabase: Creating `requests.js` in the `utils` Folder**

To handle fetching and posting todos to Supabase, we can create a file named `requests.js` within the `utils` folder. This file will contain functions that encapsulate the logic for making API requests to the Supabase database. By doing so, we can keep the API interaction centralized and easily manage todo-related requests in our Next.js project.

Let's go ahead and create the `requests.js` file:

Step 1: Create `requests.js` in the `utils` folder

In your Next.js project, navigate to the `utils` folder, which you created earlier, and create a new file named `requests.js`.

Step 2: Implement Fetch and Post Functions

In the `requests.js` file, add the necessary functions to fetch and post todos to Supabase. Below is an example of how you can implement these functions:

### Fetch Todos

```
// utils/requests.js
import { supabaseClient } from './supabaseClient';

// Function to fetch todos from Supabase
export const getTodos = async ({ userId, token }) => {
  const supabase = await supabaseClient(token);
  const { data: todos, error } = await supabase
    .from("todos")
    .select("*")
    .eq("user_id", userId);
  
  if (error) {
    console.error('Error fetching todos:', error.message);
    return [];
  }

  return todos;
};
```

The `getTodos` function is an asynchronous function that accepts an object with `userId` and `token` properties as parameters.  It first awaits the `supabaseClient` function (imported from `'./supabaseClient'`) passing the `token` as an argument. This function creates and returns the Supabase client. Using the Supabase client, it performs a select query on the "todos" table, filtering the results based on the `user_id` column that matches the `userId` parameter.
The result is destructured to extract the `data` and `error` properties. If an error occurs during the fetch operation, it logs an error message to the console and returns an empty array (`[]`).
Otherwise, it returns the fetched `todos` data.

### Post Todos

```
export const postTodo = async ({ userId, token, e }) => {
  const supabase = await supabaseClient(token);
  const { data, error } = await supabase
    .from('todos')
    .insert({
      user_id: userId,
      todo: e.target[0].value,
      tag: e.target[1].value,
    })
    .select();

  if (error) {
    console.error('Error posting todo:', error.message);
    return null;
  }

  return data;
};

```

The `postTodo` function is also an asynchronous function that accepts an object with `userId`, `token`, and `e` properties as parameters.
It awaits the `supabaseClient` function (imported from `'./supabaseClient'`) passing the `token` as an argument to create the Supabase client.
Using the Supabase client, it performs an insert operation on the "todos" table, inserting a new row with values for `user_id`, `todo`, and `tag` columns obtained from the `e` parameter (which is expected to contain event-related information).
The result is destructured to extract the `data` and `error` properties.
If an error occurs during the insert operation, it logs an error message to the console and returns `null`.
Otherwise, it returns the inserted `data`.


Now that you have created the `fetchTodos` and `postTodo` functions in the `requests.js` file, you can use them in your components or pages to interact with your Supabase database. Import the functions where needed and call them to fetch or post todos.

## Post Todos Component

To post `todos` to Supabase, create a `create-todo` folder inside the app directory, then add a `page.jsx` file inside it with the following code:

```
import { useState } from 'react';
import { useRouter } from 'next/router';
import { postTodo } from '../../utils/requests';
import { useAuth } from '@clerk/nextjs';
import Form from '@components/Form';

const CreateToDo = () => {
  const router = useRouter();
  const { userId, getToken } = useAuth();

  const [submitting, setSubmitting] = useState(false);
  const [formData, setFormData] = useState({ todo: '', tag: '' });

  const createTodo = async (e) => {
    e.preventDefault();
    try {
      setSubmitting(true);
      const token = await getToken({ template: 'supabase' });
      const posts = await postTodo({ e, userId, token });
      setFormData(posts);
      if (posts) {
        router.push('/');
      }
    } catch (error) {
      console.error('An error occurred:', error);
    } finally {
      setSubmitting(false);
    }
  };

  return (
    <Form
      type='Post'
      post={formData}
      setPost={setFormData}
      submitting={submitting}
      handleSubmit={createTodo}
    />
  );
};

export default CreateToDo;

```
The `CreateToDo` component is responsible for handling the creation of `todos` and posting them to the Supabase backend.

It imports the required dependencies, including `useState` for managing component state, `useRouter` from Next.js to access the router functionality, `postTodo` from the `utils/requests` module to send the todo data to the server, `useAuth` from `@clerk/nextjs` for authentication using Clerk, and `Form` from `@components/Form` for rendering the form.

The component uses the `useState` hook to manage the state variables `submitting` and `formData`. `submitting` is used to indicate whether the form is currently being submitted, while `formData` holds the todo and tag values from the form.

The `createTodo` function is defined to handle the form submission. It is an asynchronous function to handle the asynchronous tasks such as getting the authentication token and posting the todo data to the server.

When the form is submitted (`e.preventDefault()` is called), the function sets `submitting` to true to indicate that the form submission is in progress.

It then uses `useAuth` to obtain the user's ID and gets the Supabase authentication token using `getToken({ template: 'supabase' })`.

The `postTodo` function is called with the necessary data, including the event (`e`), the user's ID, and the authentication token, to post the todo data to Supabase.
The response from the server is stored in the `posts` variable, and the state is updated with the response data using `setFormData`.

If the post was successful (`if (posts)`), the user is redirected to the homepage using the Next.js router (`router.push('/')`).

Any errors that occur during the process are caught in the `catch` block and logged to the console for debugging purposes.

Regardless of success or failure, `setSubmitting(false)` is called in the `finally` block to set `submitting` back to false after the form submission process is completed.

Finally, the `Form` component is rendered with the appropriate props, including the `type`, `post`, `setPost`, `submitting`, and `handleSubmit`. These props are used to pass the necessary information and functions to the `Form` component to display and handle the form elements.

## Retrieve Todos Component

To retrieve the posted todos from Supabase and create a `Feed.jsx` component to display them, follow these steps:

1.  Import the necessary dependencies:

```
'use client';
import { useState, useEffect } from 'react';
import  {  getTodos  }  from  '../utils/requests';
```

Create the `Feed` component:

```
const Feed = ({ userId, token }) => {
  const [todos, setTodos] = useState([]);

  useEffect(() => {
    const fetchTodos = async () => {
      try {
        const todosData = await getTodos({ userId, token });
        setTodos(todosData);
      } catch (error) {
        console.error('Error fetching todos:', error.message);
        setTodos([]);
      }
    };

    fetchTodos();
  }, [userId, token]);

  return (
    <div>
      <h2>Todo Feed</h2>
      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>{todo.title}</li>
        ))}
      </ul>
    </div>
  );
};

export default Feed;
```
 In the `Feed` component above, the `useState` hook is used to create a state variable `todos` and the `setTodos` function to update it. The initial value of `todos` is an empty array.

The `useEffect` hook is used to fetch the todos when the component is mounted. It calls the `fetchTodos` function asynchronously, which is defined inside the `useEffect` hook.

Inside the `fetchTodos` function, the `getTodos` function is called with the provided `userId` and `token`. The returned todos data is stored in the `todosData` variable and then set as the new value of the `todos` state using `setTodos(todosData)`.

If an error occurs during the API call, it is caught in the `catch` block, logged to the console, and the `todos` state is set to an empty array to clear any previously fetched data.

The rendered JSX in the `return` statement displays the list of todos. Each todo is mapped to a `<li>` element with a unique `key` attribute set to `todo.id`, and the `todo.title` is displayed as the content of the list item.

Finally, the `Feed` component is exported as the default export.

## Conclusion

In conclusion, we successfully built a sleek Todo app using Next.js 13, Clerk for Authentication, and Supabase as the database. Clerk ensured secure access, while Supabase handled data storage and real-time updates. The integration of these technologies formed a powerful foundation for modern web apps with enhanced security and user experience. Happy coding!

Your final application should looki like this

![Clerk-Supabase](https://res.cloudinary.com/hackit-africa/image/upload/v1689783398/clerk-supabase.gif)

## Reference 

For further guidance and information, refer to the following:

1.  Next.js Documentation: Access the official Next.js documentation at [https://nextjs.org/docs](https://nextjs.org/docs) for in-depth details about the framework.
    
2.  Clerk Documentation: Explore Clerk's authentication capabilities and integration guides at [https://clerk.com/docs](https://clerk.com/docs) to understand its usage better.
    
3.  Supabase Documentation: Learn about Supabase's real-time database features and integration options at [https://supabase.com/docs](https://supabase.com/docs).
    
4.  Simplifying Authentication in Next.js Applications with Clerk: Find detailed steps for setting up Clerk authentication in Next.js apps in the blog post: [Dev.to Link](https://dev.to/musebe/simplifying-authentication-in-nextjs-applications-with-clerk-3adh).
    

These references will aid you in building the Todo app with Next.js 13, Clerk, and Supabase effectively. Keep them handy for quick access to valuable information during your development journey.