Build a simple E-commerce PIM with Next.js, Prisma, and Neon
Tags: nextjs, webdev, prisma, tutorial
Published: 2024-03-09 20:27:50

This article will walk you through the steps of creating an e-commerce product information management system (PIM) with the Next.js framework, a Neon Postgres database, and Prisma as the object-relational mapper (ORM).

Next.js is a React framework for building fast and user-friendly full-stack applications. It provides numerous features and tools for building simple and complex web pages.

[Prisma](https://www.prisma.io/?utm_source=hackmamba&utm_medium=blog&utm_id=HMBcommunity) is a server-side library that helps developers read and write data to the database intuitively, efficiently, and safely.

[Neon](https://neon.tech/?ref=hm) is a fully managed serverless Postgres with a generous free tier that offers a scalable and cost-effective solution for data storage, and modern developer features such as branching and bottomless storage. 

This article won’t cover cart, checkout, and deployment features but will focus on how to create, read, update, and delete (CRUD) products in the database.

Here is a completed [d](https://neon-ecommerce-eta.vercel.app/)[emo](https://neon-ecommerce-eta.vercel.app/?utm_source=guides&utm_medium=content&utm_campaign=hackmamba) of this project.


## Prerequisites

You’ll need the following to complete this tutorial:

- Node.js installed on your computer.
- Basic knowledge of [React](https://react.dev/) and [Next.js](https://nextjs.org/)
- A [Git](https://github.com/)[H](https://github.com/)[ub](https://github.com/) account for (OAUTH) authentication


## Getting started

We'll start with installing the Next.js package and other dependencies we'll use for this project. Open the terminal and run the below command to create a next.js starter project.


    npx create-next-app neon-ecommerce

The above command will prompt a few questions; choose yes when asked whether to use Tailwind CSS. A new project with the appropriate configurations will be created in the selected directory; in our case, it is called neon-e-commerce.

Next, navigate to the project directory and install [@prisma/client](https://www.npmjs.com/package/@prisma/client), [next-auth](https://www.npmjs.com/package/next-auth?utm_source=hackmamba&utm_medium=blog&utm_id=HMBcommunity), and [Prisma](https://www.npmjs.com/package/prisma?utm_source=hackmamba&utm_medium=blog&utm_id=HMBcommunity) npm packages with the following command:


    cd neon-ecommerce 
    npm i @prisma/client next-auth && npm i prisma --save-dev

Next, run the following command to launch the app:


    npm run dev

Next.js will start a live development server at [http://localhost:3000](http://localhost:3000).


## Setting up a Neon database

[Log in](https://console.neon.tech) or create a Neon account; Neon provides one free tier account with a limit of one project per account.


![Create a project](https://paper-attachments.dropboxusercontent.com/s_E82647503A5B486A9F4CD867B701BD9FB77EF3904DACD164B2294D0FB2C33367_1700826227251_Screenshot+2023-11-24+at+12.43.32.png)


Click the “**Create project”** button and copy the database URL provided by Neon on the next page; this is all we need to connect the database to our project.


![Our Neon dashboard](https://paper-attachments.dropboxusercontent.com/s_E82647503A5B486A9F4CD867B701BD9FB77EF3904DACD164B2294D0FB2C33367_1700830625247_Screenshot+2023-11-24+at+12.50.16.png)



## Setting up GitHub authentication

Since we'll be using GitHub for authentication, we need to register a new OAuth app on GitHub. This authentication is for admin users who want to create and sell their products, not for customers who wish to buy.

First, sign in to [GitHub](https://github.com) and go to Settings > Developer Settings > OAuth Apps.

Next, click on the "New OAuth App" button and fill out the form like in the image below:

![Registering OAuth app](https://paper-attachments.dropboxusercontent.com/s_E82647503A5B486A9F4CD867B701BD9FB77EF3904DACD164B2294D0FB2C33367_1700835193473_Screenshot+2023-11-24+at+15.10.47.png)


Learn more about [next-auth](https://next-auth.js.org/configuration/providers/oauth?utm_source=hackmamba&utm_medium=blog&utm_id=HMBcommunity) [o](https://next-auth.js.org/configuration/providers/oauth)[auth](https://next-auth.js.org/configuration/providers/oauth) providers (e.g., Twitter, Google, GitHub, etc.).

After registering the application, copy the client ID and generate a new client secret.

![Showing client ID and Client secret](https://paper-attachments.dropboxusercontent.com/s_E82647503A5B486A9F4CD867B701BD9FB77EF3904DACD164B2294D0FB2C33367_1700836827173_Screenshot+2023-11-24+at+15.31.04.png)


Next, in the root directory of the project, create a `.env` file and set up environment variables as seen below:

```js
    #.env
    DATABASE_URL="<YOUR NEON DATABASE URL>"
    GITHUB_ID="<YOUR GITHUB CLIENT ID>"
    GITHUB_SECRET="<THE GENERATED CLIENT SECRET>"
    NEXTAUTH_URL="http://localhost:3000/api/auth"
    NEXTAUTH_SECRET="<RUN *openssl rand -base64 32* TO GENERATE A SECRET>"
```

Learn more about the [next-auth secret](https://next-auth.js.org/deployment?utm_source=hackmamba&utm_medium=blog&utm_id=HMBcommunity) and how it works.

Now that we've completed the setup and installation, let's start developing the application.


## Building the application

The first thing we'll do is create a **component** folder in the root of our application and a `Header.js` file with the following snippet: 

```js
    //component/Header.js
    import React from "react";
    import Link from "next/link";
    import { useRouter } from "next/router";
    import { signOut, useSession } from "next-auth/react";
    
    const Header = () => {
      const router = useRouter();
      const isActive = (pathname) => router.pathname === pathname;
      const { data: session } = useSession();
    
     // #When there is NO User
    let left = (
        <div className="left">
          <Link legacyBehavior href="/">
            <a className="bold" data-active={isActive("/")}>
              Neon Ecommerce Shop
            </a>
          </Link>
         
        </div>
      );
    
      let right = null;
    
      if (!session) {
        right = (
          <div className="right">
            <Link legacyBehavior href="/api/auth/signin">
              <a data-active={isActive("/signup")}>Log in</a>
            </Link>
        
          </div>
        );
      }
    
      }
      return (
        <nav>
          {left}
          {right}
        </nav>
      );
    };
    export default Header;
```

In the snippets above, we:

- Imported the `useRouter` hook from `next/router` to handle navigating to different routes.
- Imported `useSession` from `next-auth` to show our left and right `nav` when no user exists.

Next, let’s handle our `nav` for when there is a logged-in user; add the following snippets to the `Header.js` file:

```js
    // When there is a user
    if (session) {
        left = (
          <div className="left">
            <Link legacyBehavior href="/">
              <a className="bold" data-active={isActive("/")}>
                Products
              </a>
            </Link>
            <Link legacyBehavior href="/p/own">
              <a data-active={isActive("/p/own")}>My Products</a>
            </Link>
          </div>
        );
        right = (
          <div className="right">
            <p>
              {session.user.name} ({session.user.email})
            </p>
            <Link legacyBehavior href="/p/create">
              <button>
                <a>New Product</a>
              </button>
            </Link>
            <button onClick={() => signOut()}>
              <a>Log out</a>
            </button>
           
          </div>
        );
```

When a user is logged in, we display the `Product`, `My Products`, `New Product`, and `signOut` navigations.

Next, let’s add a `Layout.js`  file within the component folder, which will manage our page layouts and help us render the `Header.js` component throughout the application pages.

```js
    //component/Header.js
    import React from "react";
    import Header from "./Header";
    const Layout = (props) => (
      <div>
        <Header />
        <div >{props.children}</div>
      </div>
    );
    export default Layout;
   ``` 

Next, import the `Layout.js` component inside the `pages/index.js` file and render it to see how it looks.

```js
    //pages/index.js
    import React from "react";
    import Layout from "../components/Layout";
    const Products = () => {
      return (
        <Layout>
          <div>Hello from Homepage</div>
        </Layout>
      );
    };
    export default Products;
 ```   

In the browser, our application will look like the image below:


![Showing the navigations on our homepage](https://paper-attachments.dropboxusercontent.com/s_E82647503A5B486A9F4CD867B701BD9FB77EF3904DACD164B2294D0FB2C33367_1701207877151_Screenshot+2023-11-28+at+22.44.05.png)


Currently, the login button redirects us to the `pages/api/auth` route, which we are yet to develop. 
Before we create the authentication route, let's generate our app schema, fill it with placeholder data, and read it so that the homepage looks a little more interesting.

## Generating schema, creating and reading data

First, let’s create a `prisma` folder in the root of our project and create a `schema.prisma` file with the following snippets:

```js
    //prisma/schema.prisma
    generator client {
      provider = "prisma-client-js"
    }
    
    datasource db {
      provider  = "postgresql"
      url       = env("DATABASE_URL") // uses connection pooling
    }
    
    model Product {
      id           String   @id @default(cuid())
      name         String?
      image        String?
      price        String?
      publish      Boolean  @default(true)
      productOwner Owner?   @relation(fields: [ownerId], references: [id])
      ownerId      String?
    }
    
    model Owner {
      id        String    @id @default(cuid())
      name      String?
      email     String?   @unique
      createdAt DateTime  @default(now()) @map(name: "created_at")
      updatedAt DateTime  @updatedAt @map(name: "updated_at")
      products  Product[]
    
      @@map(name: "owners")
    }
```

Before running the `npx p``risma` `db` `push` command to publish our schema, you can use the format command if you’re not confident everything was formatted correctly.


    npx prisma format #formats the schema
    npx prisma db push #publishes the schema

After clicking on the table navigation in the [neon console](https://console.neon.tech/app/projects) after publishing, we’ll see the `Product` and `Owner` models.


![Showing the tables we published](https://paper-attachments.dropboxusercontent.com/s_E82647503A5B486A9F4CD867B701BD9FB77EF3904DACD164B2294D0FB2C33367_1700861101003_Screenshot+2023-11-24+at+22.24.42.png)


At this stage, the Prisma studio is the fastest way to create data; to launch the studio and add data, run the following command:


    npx prisma studio

The studio will launch at http://localhost:5555/

Next, create a few mock products in the studio, each with a proper image URL for the product image. Each product must have an owner, select an existing user in the database, or create a new user record.


## Fetching and reading data from database

Now that we have successfully created some product data, let's fetch those products and render them on our homepage.

We must establish a [Prisma client](https://www.prisma.io/docs/orm/prisma-client?utm_source=hackmamba&utm_medium=blog&utm_id=HMBcommunity) instance to enable smooth interaction with our Neon database. In the root of our application, create a `lib` folder and a `prisma.js` file with the following snippets:

```js
    //lib/prisma.js
    import { PrismaClient } from "@prisma/client";
    let prisma;
    if (process.env.NODE_ENV === "production") {
      prisma = new PrismaClient();
    } else {
      if (!global.prisma) {
        global.prisma = new PrismaClient();
      }
      prisma = global.prisma;
    }
    export default prisma;
  ```  

Next, let’s create a `Product.js` file in the component folder with the below snippets:

```js
    //component/Product.js
    import Router from "next/router";
    import Image from "next/image";
    
    function Product({ product }) {
      return (
        <div onClick={() => Router.push(`/p/${product.id}`)}>
          <div>
            <img
              width={500}
              height={500}
              className="w-full"
              src={product.image}
              alt="item image"
            />
            <div>
              <div>{product.name}</div>
              <p>#{product.price}</p>
            </div>
            <div>
              <span>Add to cart</span>
            </div>
          </div>
        </div>
      );
    }
    export default Product;
```

We will receive individual products in the snippets above and display their details.  

Let's fetch the products from our database and show them on the homepage. Update the `index.js` file like below.

```js
    //pages/index.js
    import prisma from "@/lib/prisma";
    import Layout from "@/components/Layout";
    import Product from "@/components/Product";
    
    export const getStaticProps = async () => {
      const products = await prisma.product.findMany({
        where: { publish: true },
        include: {
          productOwner: {
            select: { name: true, email: true },
          },
        },
      });
      return {
        props: { products },
        revalidate: 10,
      };
    };
    
    const Products = (props) => {
      return (
        <>
          <Layout>
            <div className="grid grid-cols-3 gap-2">
              {props.products.map((product) => (
                <Product key={product.id} product={product} />
              ))}
            </div>
          </Layout>
        </>
      );
    };
    export default Products;
``` 

In the snippets above, we:

- Used the `prisma` instance to fetch our published products, then provided them as `props` in the `getStaticProps()` function to our `Products()` function.
- Looped through the `products` props and used our `Product` component to render each product.


![All user product page with a log in button](https://paper-attachments.dropboxusercontent.com/s_E82647503A5B486A9F4CD867B701BD9FB77EF3904DACD164B2294D0FB2C33367_1701186973381_Screenshot+2023-11-28+at+16.55.19.png)


Clicking on each product card directs us to the `/p/${product.id}` route, which doesn’t exist yet. Let's create that route first before we handle authentication.

Inside the `pages` directory, create a `p` folder and `[id].js` file with the following snippet:

```js
    //pages/p/[id].js
    import Layout from "@/components/Layout";
    import Product from "@/components/Product";
    import prisma from "@/lib/prisma";
    
    export const getServerSideProps = async ({ params }) => {
      const product = await prisma.product.findUnique({
        where: {
          id: String(params?.id),
        },
        include: {
          productOwner: {
            select: { name: true, email: true },
          },
        },
      });
      return {
        props: product,
      };
    };
    function HandleProducts(props) {
      return (
        <Layout>
          <div className="max-w-4xl ">
            <Product product={props} />
          </div>
        </Layout>
      );
    }
    export default HandleProducts;
```    

In the above snippets, we:

- Got the product `id` from the `params` and fetched the product details using the `prisma` instance in the `getServerSideProps()` function.
- Displayed the product using our `Product` component inside the `HandleProducts()` function.

Now the `/p/${product.id}` route is in place! Clicking on any product card takes us to the corresponding page with the specific product information.


## Authenticating users with GitHub OAuth provider

Recall that clicking the **login** button takes us to an uncreated `api/auth` route; let's create it now. 

Create an `auth` folder and a `[...nextauth].js` file in the `api` folder located inside the `pages` directory using the following snippet:

```js
    //pages/api/auth/[...nextauth].js
    import NextAuth from "next-auth";
    import GithubProvider from "next-auth/providers/github";
    export const authOptions = {
      providers: [
        GithubProvider({
          clientId: process.env.GITHUB_ID,
          clientSecret: process.env.GITHUB_SECRET,
        }),
        // ...add more providers here
      ],
      secret: process.env.NEXTAUTH_SECRET,
    };
    export default NextAuth(authOptions);
```    

In the above snippet, we imported `GithubProvider` and gave it our **GitHub id** and **GitHub secret** variables.

Now, when we click on the **login** button, we will be directed to a page where a "Sign in with GitHub" button is shown.

![Sign in with GitHub button](https://paper-attachments.dropboxusercontent.com/s_E82647503A5B486A9F4CD867B701BD9FB77EF3904DACD164B2294D0FB2C33367_1701187124542_Screenshot+2023-11-28+at+16.58.16.png)


We will be redirected to our application after signing in with our credentials.

![Admin user products page with updated navigations](https://paper-attachments.dropboxusercontent.com/s_E82647503A5B486A9F4CD867B701BD9FB77EF3904DACD164B2294D0FB2C33367_1701186563322_Screenshot+2023-11-28+at+16.48.49.png)


Notice that our navigation has been updated to include the logged-in user's name, `MyProducts`, `NewProduct` navigations, and a `logout` button.


## Adding new products

Next, let's create the **New Product** page for authenticated users to create and publish their products. First, create a reusable form component to collect the product details. In the `components` directory, create a `Product-Form` file with the following snippet:

```js
    //components/Product-Form.js
    import React from "react";
    function ProductForm({
      productName,
      productPrice,
      productImage,
      publish,
      setProductName,
      setProductPrice,
      setProductImage,
      setPublish,
      handleSubmit,
      handleCancel,
    }) {
      const checkHandler = (e) => {
        e.preventDefault();
        setPublish(!publish);
      };
      return (
        <>
          <div>
            <form onSubmit={handleSubmit}>
              {/* FORM INPUTS HERE */}
              </form>
          </div>
        </>
      );
    }
    export default ProductForm;
```    

In the snippet above:

- We destructured the product properties in the `ProductForm` component function and used the `publish` and `setPublish` properties to create a `checkHandler()` function.
- In the `return()` function, we returned a `form` tag and passed `handleSubmit` props to the form’s `onSubmit` function.

Next, let’s add the following snippet inside the `form` tag.

```js
    //components/Product-Form.js
    <div>
    <div>
      <label htmlFor="product-name">Name</label>
      <input
        id="product-name"
        type="text"
        value={productName}
        onChange={(e) => setProductName(e.target.value)}
        placeholder="Product title"
        required
      />
    </div>
    <div>
      <label htmlFor="product-price">Price</label>
      <input
        id="product-price"
        type="text"
        value={productPrice}
        onChange={(e) => setProductPrice(e.target.value)}
        placeholder="Amount"
        required
      />
    </div>
    </div>
    <div>
    <div>
      <label htmlFor="product-image">Product URL</label>
      <input
        id="product-image"
        type="text"
        value={productImage}
        onChange={(e) => setProductImage(e.target.value)}
        placeholder="Image URL"
        required
      />
    </div>
    </div>
    <div>
    <div>
      <label>
        <input
          type="checkbox"
          checked={publish}
          onChange={checkHandler}
        />
        <span>Publish Immediately?</span>
      </label>
    </div>
    <div>
      <input type="submit" value="Submit" />
      <button onClick={handleCancel} type="button">
        Cancel
      </button>
    </div>
    </div>
```    

In the snippet above, we added various input fields for the product data, a check box to determine whether the product should be published immediately, a button to initiate the submit action, and another to cancel the process.

To show the product form in the frontend, create a `create.js` file inside the `p` directory using the following snippet:

```js
    // pages/p/create
    import React, { useState } from "react";
    import Layout from "@/components/Layout";
    import Router from "next/router";
    import ProductForm from "@/components/Product-Form";
    
    const CreateProduct = () => {
      const [productName, setProductName] = useState("");
      const [productPrice, setProductPrice] = useState("");
      const [productImage, setProductImage] = useState("");
      const [publish, setPublish] = useState(false);
      
      const create = async (e) => {
        e.preventDefault();
        try {
          const body = { productName, productPrice, productImage, publish };
          await fetch("/api/product/create", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify(body),
          });
          await Router.push("/p/own");
        } catch (error) {
          console.error(error);
        }
      };
      return (
        <Layout>
          <ProductForm
            handleSubmit={(e) => create(e)}
            publish={publish}
            setPublish={setPublish}
            productImage={productImage}
            setProductImage={setProductImage}
            productName={productName}
            setProductName={setProductName}
            productPrice={productPrice}
            setProductPrice={setProductPrice}
            handleCancel={() => Router.push("/p/own")}
          />
        </Layout>
      );
    };
    export default CreateProduct;
```    

In the snippet above:

- We imported the `ProductForm` component and defined state variables to hold our product data when a user enters it in the form.
- We also wrote a `create()` function, which collects all product data and sends it as a POST request to the `/api/product/create` route, which we'll create shortly.
- In the `return()` function, we rendered the `ProductForm` and passed the state constants and the `create()` function to it.

When we navigate to the **New Product** page, we’ll see the form rendered like below.

![New product form](https://paper-attachments.dropboxusercontent.com/s_E82647503A5B486A9F4CD867B701BD9FB77EF3904DACD164B2294D0FB2C33367_1701186357059_Screenshot+2023-11-28+at+16.45.22.png)


Now, let's create the route we're sending the product data to; in the `api` folder, create a `product` folder, a `create` folder, and an `index.js` file with the following snippet:

```js
    //api/product/create/index.js
    import { getServerSession } from "next-auth/next";
    import { authOptions } from "../../auth/[...nextauth]";
    import prisma from "@/lib/prisma";
    
    export default async (req, res) => {
      const session = await getServerSession(req, res, authOptions);
      const { productName, productPrice, productImage, publish } = req.body;
      if (session) {
        const result = await prisma.product.create({
          data: {
            name: productName,
            price: productPrice,
            image: productImage,
            publish: publish,
            productOwner: {
              connectOrCreate: {
                where: {
                  email: session.user.email,
                },
                create: {
                  email: session.user.email,
                  name: session.user.name,
                },
              },
            },
          },
        });
        res.json(result);
      } else {
        // Not Signed in
        res.status(401);
      }
      res.end();
    };
```    

In the snippet above:

- We created a session constant with the getSeverSession hook and destructured `productName`, `productPrice`, `productImage`, and `publish` from the request body.


- We checked to see if the user making the request has an active session, and if so, we called the `prisma.``product.create()` method and gave our data, which included the `productName`, `productPrice`, `productImage`, and `publish`. 

Because each product will have an owner, the `connectOrCreate` method determines whether the authorized user attempting to create the product has a record in the database and connects the product if a record exists or creates a new user if none exists.


## Updating a product

Since users can now successfully create products, there may be a need to edit the product details. Let's implement the update functionality to allow users to update their products.

In the `p` directory within `pages`, update the `[id].js` file like in the below:

```js
    //pages/p/[id].js
    // OTHER IMPORTS HERE
    import ProductForm from "@/components/Product-Form";
    // getServerSideProps() HERE
    function HandleProducts(props) {
      
      const [isVisible, setIsVisible] = useState(false);
      const [productName, setProductName] = useState("");
      const [productPrice, setProductPrice] = useState("");
      const [productImage, setProductImage] = useState("");
      const [published, setPublished] = useState(false);
      
      const { id, publish } = props;
      
      const userHasValidSession = Boolean(session);
      const productBelongsToUser = session?.user?.email === props.productOwner?.email;
      
      const updateProduct = async () => {
        try {
          const body = { productName, productPrice, productImage, published };
          await fetch(`/api/product/update/${id}`, {
            method: "PUT",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify(body),
          });
          await Router.push("/p/own");
        } catch (error) {
          console.error(error);
        }
      };
      return (
        <Layout>
          <div>
            <div>
              {/* PRODUCT COMPONENT HERE */}
              {userHasValidSession && productBelongsToUser && (
                <div className="inline-flex mt-5 py-3">
                  <button
                    onClick={() => setIsVisible(true)}
                  >
                    Edit
                  </button>
                </div>
              )}
            </div>
            <div>
              {isVisible && (
                <ProductForm
                  handleSubmit={() => updateProduct()}
                  publish={published}
                  setPublish={setPublished}
                  productImage={productImage}
                  setProductImage={setProductImage}
                  productName={productName}
                  setProductName={setProductName}
                  productPrice={productPrice}
                  setProductPrice={setProductPrice}
                  handleCancel={() => setIsVisible(false)}
                />
              )}
            </div>
          </div>
        </Layout>
      );
    }
    export default HandleProducts;
```    

In the snippet above, we:

- Imported the `ProductForm` component and defined state constants to keep the new product data as they are entered.
- Extracted the ID and `publish` from `props` returned by the `getServerSideProps()` function.
- Created the `userHasValidSession` and `productBelongsToUser` constants to determine whether the user has an active session and whether the product belongs to the user.
- The `updateProduct()` function will bundle the updated product details and send them as a PUT request to the `api/product/update/${id}` route we'll create soon.
- Showed the **Edit** button in the `return()` function only if the user has an active session and owns the product; the **Edit** button sets the `isVisible` state constant to `true`.
- Displayed the `ProductForm` component only if the `isVisible` value is `true`, and we passed `updateProduct()` to the form.

Next, let’s create the `api/product/update/${id}` route with the following snippet:

```js
    //api/product/update/[id].js
      import prisma from "@/lib/prisma";
    
      export default async function handleUpdate(req, res) {
      
      const productId = req.query.id;
      const { productName, productPrice, productImage, publish } = req.body;
      
      if (req.method === "PUT") {
        const product = await prisma.product.update({
          where: { id: productId },
          data: {
            name: productName,
            price: productPrice,
            image: productImage,
            publish: publish,
          },
        });
        res.json(product);
      } else {
        throw new Error(
          `Updating product with ID: ${productId} was not sucessful`
        );
      }
    }
```    

In the snippet above:

- We imported the `prisma` instance, set `productId` constant equal to `req.query.id`, and destructured the product properties from the request body.
- We used the `prisma` instance to update the product, passing the `productId` as the id and the product properties as the `data`.

Now, users can successfully create and update products. But what if they want to publish or unpublish a product without performing a full update? Let's add the publish and unpublish functions next.

## Adding publish and unpublish functions

If the product is unpublished, we want to show a publish button; otherwise, we want to offer an unpublish button. Update the `[id].js` file inside the `p` directory with the following snippet:

```js
    //pages/p/[id].js
    // IMPORTS HERE
    // getServerSideProps() FUNCTION HERE
    function HandleProducts(props) {
      //  STATE CONSTANTS HERE
      const { id, publish } = props;
      const userHasValidSession = Boolean(session);
      const postBelongsToUser = session?.user?.email === props.productOwner?.email;
      
      async function handlePublish(id) {
        const body = { publish };
        await fetch(`/api/product/publish/${id}`, {
          method: "PUT",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify(body),
        });
        await Router.push("/p/own");
      }
      //   updateProduct() FUNCTION HERE
      return (
        <Layout>
          <div>
            <div>
              {/* PRODUCT COMPONENT HERE  */}
              {userHasValidSession &&
                postBelongsToUser &&
                (!props.publish ? (
                  <button onClick={() => handlePublish(id)}>Publish</button>
                ) : (
                  <button onClick={() => handlePublish(id)}>Unpublish</button>
                ))}
              {/* EDIT BUTTON HERE */}
            </div>
            {/* PRODUCTFORM COMPONENT HERE */}
          </div>
        </Layout>
      );
    }
    export default HandleProducts;
```    

Here:

- We defined the `handlePublish()` function, saved the product's current publish status as the `body` constant, and sent it to the `'/api/product/publish/${id}'` route we will define shortly.
- The buttons were conditionally rendered after determining whether the user had an active session and whether the product about to be published or unpublished belonged to the stated user.

Next, let’s create the `'/api/product/publish/${id}'`  route with the following snippet:

```js
    //api/product/publish/
    import prisma from "@/lib/prisma";
    
      export default async function handleActive(req, res) {
      const productId = req.query.id;
      const { publish } = req.body;
      const product = await prisma.product.update({
        where: { id: productId },
        data: { publish: !publish },
      });
      res.json(product);
    }
```    

Here:

- We imported our `prisma` instance and destructured the `publish` constant from the request body.
- We updated the product using the `prisma` instance and changed the `publish` data to the reverse of what it is. 

Now, users can create, update, publish, and unpublish a product; sometimes, a user may want to delete a product completely. Let’s add the delete function.

## Deleting a product

```js
    //pages/p/[id].js
    
    // IMPORTS HERE
    // getServerSideProps() FUNCTION HERE
    //Delete Product
    async function deleteProduct(id) {
      if (confirm("Delete this product?")) {
        await fetch(`/api/product/delete/${id}`, {
          method: "DELETE",
        });
        Router.push("/p/own");
      } else {
        Router.push(`/p/${id}`);
      }
    }
    function HandleProducts(props) {
      // STATE CONSTANTS HERE
      const { id, publish } = props;
      const userHasValidSession = Boolean(session);
      const postBelongsToUser = session?.user?.email === props.productOwner?.email;
      //  handlePublish() FUNCTION HERE
      //  updateProduct() FUNCTION HERE
      return (
        <Layout>
          <div>
            <div>
              {/* PRODUCT COMPONENT HERE  */}
              {/* PUBLISH AND UNPUBLISH BUTTONS HERE  */}
              {userHasValidSession && postBelongsToUser && (
                <div className="inline-flex mt-5 py-3">
                  <button onClick={() => deleteProduct(id)}>Delete</button>
                  {/* Edit BUTTON HERE  */}
                </div>
              )}
            </div>
            {/* ProductForm COMPONENT HERE  */}
          </div>
        </Layout>
      );
    }
    export default HandleProducts;
```    

In the above snippet:

- We constructed the `deleteProduct()` function, which takes the id of the product we want to delete and sends it to the ``/api/product/delete/${id}`` route we'll develop shortly.
- We added a **Delete** button before the **Edit** button, called the `deleteProduct()` function, and supplied the product's `id` we wanted to delete.

The complete snippet of the `pages/p/[id].js` can be found in this [GitHub](https://gist.github.com/Kizmelvin/fd59c50d09295f83809760367c951f9d?utm_source=hackmamba&utm_medium=blog&utm_id=HMBcommunity) [gist](https://gist.github.com/Kizmelvin/fd59c50d09295f83809760367c951f9d).

Next, let’s implement the ``/api/product/delete/${id}`` route  with the following snippet:


```js    
    import prisma from "@/lib/prisma";
    export default async function handleDelete(req, res) {
      const productId = req.query.id;
      if (req.method === "DELETE") {
        const product = await prisma.product.delete({
          where: { id: productId },
        });
        res.json(product);
      } else {
        throw new Error(
          `The HTTP ${req.method} method is not supported at this route.`
        );
      }
    }
```    

Here, we:

- We imported our `prisma` instance and created a `productId`, which we set equal to the request query ID.
- The `prisma` sample was used to delete the product, with the `productId` as the data.

A user who has successfully authenticated can now add new products and update, publish, and delete any product that belongs to them.

![Single product page where the product belongs to the user](https://paper-attachments.dropboxusercontent.com/s_E82647503A5B486A9F4CD867B701BD9FB77EF3904DACD164B2294D0FB2C33367_1701186125576_Screenshot+2023-11-28+at+16.41.19.png)



## Conclusion 

Congratulations! You successfully built a full-stack application with CRUD functionalities with Next.js, Neon, Prisma, and Next-Auth. Combining these tools can help developers create full-stack applications that are efficient and scalable. Next.js provides a structured development framework, Prisma streamlines database interactions, and Neon delivers a cost-effective and scalable database solution. The source code for this project is in this [GitHub](https://github.com/Kizmelvin/neon-ecommerce-app?utm_source=hackmamba&utm_medium=blog&utm_id=HMBcommunity) [repository.](https://github.com/Kizmelvin/neon-ecommerce-app)