DEV Community

Cover image for Building An E-Commerce Store With NextJS
Emil Pearce for novu

Posted on • Originally published at novu.co

Building An E-Commerce Store With NextJS

In this tutorial, you'll learn how to build an e-commerce store where customers can purchase products and make payments via Stripe. After a successful payment, an email notification is sent to the customer, and an in-app notification to the Admin user. The Admin user can also create and delete products within the application.

To build this application, we'll use the following tools:

  • Appwrite - for authenticating users, as well as saving and retrieving product details.
  • Next.js - for creating the application’s user interface and backend.
  • Novu  - for sending email and in-app notifications.
  • React Email - for creating email templates.
  • Stripe - for integrating a payment checkout to the application.

app


Building the application interface with Next.js

The application pages are divided into two parts based on the roles assigned to the users. Customers can access the Home page and sign in to the application before making payments. Admin users can access all pages, including a sign-in page and a dashboard page where they can add and remove products.

Now, let's build the application.

https://media1.giphy.com/media/iopxsZtW2QVRs4poEC/giphy.gif?cid=7941fdc6aot3qt7vvq4voh5c1iagyusdpuga713m8ljqcqmd&ep=v1_gifs_search&rid=giphy.gif&ct=g

Create a new Next.js Typescript project by running the code snippet below:

npx create-next-app novu-store
Enter fullscreen mode Exit fullscreen mode

Next, install React Icons and the Headless UI package. React Icons allows us to use various icons within the application, while Headless UI provides easy-to-use modern UI components.

npm install react-icons @headlessui/react
Enter fullscreen mode Exit fullscreen mode

Copy this code snippet from the GitHub repository into the app/page.tsx file. It renders a list of products on the screen and allows users to select items in a cart, similar to the image below.

1

Create a login route that enables users to sign using their GitHub account. Copy the code snippet below into the app/login/page.tsx file.

//👉🏻 create a login folder containing a page.tsx file
export default function Home() {
    const handleGoogleSignIn = async () => {};

    return (
        <main className='w-full min-h-screen flex flex-col items-center justify-center'>
            <h2 className='font-semibold text-3xl mb-2'>Customer Sign in</h2>
            <p className='mb-4 text-sm text-red-500'>
                You need to sign in before you can make a purchase
            </p>
            <button
                className='p-4 border-[2px] border-gray-500 rounded-md hover:bg-black hover:text-white w-2/3'
                onClick={() => handleGoogleSignIn()}
            >
                Sign in with GitHub
            </button>
        </main>
    );
}
Enter fullscreen mode Exit fullscreen mode

Customer Sign In

When users click the Sign in button, it redirects them to the GitHub authentication page and prompts them to sign in to the application. You'll learn how to do this with Appwrite shortly.

Next, let's create the admin pages. Add an admin folder containing a login and dashboard route within the app folder.

cd app
mkdir admin && cd admin
mkdir dashboard login
Enter fullscreen mode Exit fullscreen mode

Add a page.tsx file within the dashboard and login folders, and copy the code snippet below into the login/page.tsx file.

"use client";
import Link from "next/link";
import { useState } from "react";

export default function Login() {
    const [email, setEmail] = useState<string>("");
    const [password, setPassword] = useState<string>("");

    const handleLogin = async (e: React.FormEvent) => {
        e.preventDefault();
        console.log({ email, password });
    };

    return (
        <main className='w-full min-h-screen flex flex-col items-center justify-center'>
            <h2 className='font-semibold text-3xl mb-4'> Admin Sign in</h2>
            <form className='w-2/3' onSubmit={handleLogin}>
                <label htmlFor='email' className='block'>
                    Email
                </label>
                <input
                    type='email'
                    id='email'
                    className='w-full px-4 py-3 border border-gray-400 rounded-sm mb-4'
                    required
                    value={email}
                    placeholder='admin@admin.com'
                    onChange={(e) => setEmail(e.target.value)}
                />

                <label htmlFor='password' className='block'>
                    Password
                </label>
                <input
                    type='password'
                    id='password'
                    className='w-full px-4 py-3 border border-gray-400 rounded-sm mb-4'
                    required
                    value={password}
                    placeholder='admin123'
                    onChange={(e) => setPassword(e.target.value)}
                />
                <button className='p-4 text-lg mb-3 bg-blue-600 text-white w-full rounded-md'>
                    Sign in
                </button>
                <p className='text-sm text-center'>
                    Not an Admin?{" "}
                    <Link href='/login' className='text-blue-500'>
                        Sign in as a Customer
                    </Link>
                </p>
            </form>
        </main>
    );
}
Enter fullscreen mode Exit fullscreen mode

The code snippet above renders a form that accepts the Admin's email and password, validates the credentials, and then logs the user into the application.

Admin Sign In

The Admin dashboard page renders the available products and allows the Admin user to add and delete products from the application. Copy this code snippet into the dashboard/page.tsx file to create the user interface.

2

Congratulations! You've built the application interface. In the upcoming sections, you'll learn how to connect the application to an Appwrite backend and send data between the client and the server.


How to add Appwrite to a Next.js application

Appwrite is an open-source backend service that enables you to create secure and scalable software applications. It offers features such as multiple authentication methods, a secure database, file storage, cloud messaging, and more, which are essential for building full-stack applications.

In this section, you'll learn how to set up an Appwrite project, including features such as authentication, database, and file storage.

First, visit Appwrite Cloud, and create an account and organization for your projects.

Next, create a new project and select your preferred region for hosting the project.

Appwrite 1

Select Web as the platform SDK for the application.

Appwrite 2

Follow the steps displayed on the screen. Since you're currently building in development mode, you can use the wildcard (*) as your hostname and change it to your domain name after deploying the application.

Appwrite 3

Install the Appwrite client SDK within your Next.js project.

npm install appwrite
Enter fullscreen mode Exit fullscreen mode

Finally, create an appwrite.ts file within your Next.js app folder and copy the code snippet below into the file to initialize Appwrite.

import { Client, Account, Databases, Storage } from "appwrite";

const client = new Client();

client
    .setEndpoint("https://cloud.appwrite.io/v1")
    .setProject(<YOUR_PROJECT_ID>);

export const account = new Account(client);

export const db = new Databases(client);

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

Setting up GitHub Authentication with Appwrite

Here, you'll learn how to set up GitHub and Email/Password authentication with Appwrite. Email/Password authentication is already configured by default, so let's focus on setting up GitHub authentication.

Before we proceed, you need to create a GitHub OAuth application using your GitHub account. Appwrite will require the client ID and secrets to set up GitHub authentication.

GitHub 1

Enable Appwrite's GitHub authentication method by selecting Auth from the sidebar menu and navigating to the Settings tab.

Appwrite 4

Copy your GitHub client ID and secret into the Appwrite's GitHub OAuth settings.

Screenshot 2024-05-24 at 19.45.36.png

Finally, ensure you copy the URI generated by Appwrite into your GitHub app settings.

GitHub 2

Setting up Appwrite Database

Select Databases from the sidebar menu and create a new database. You can name it novu store.

Appwrite 5

Next, create a products collection. It will contain the lists of products within the application.

Appwrite 6

Add name, price, and image attributes to the collection.

Appwrite 7

Under the Settings tab, update the permissions to allow every user to perform CRUD operations. However, you can change this after deploying the application to ensure that only authenticated users can perform various actions.

Appwrite 8

Finally, copy your project, database, and collection IDs into an .env.local file. This keeps your credentials safe and allows you to reference each value from its environment variables.

NEXT_PUBLIC_PROJECT_ID=<YOUR_PROJECT_ID>
NEXT_PUBLIC_DB_ID=<YOUR_DATABASE_ID>
NEXT_PUBLIC_PRODUCTS_COLLECTION_ID=<YOUR_DB_COLLECTION_ID>
Enter fullscreen mode Exit fullscreen mode

Setting up Appwrite Storage

Select Storage from the sidebar menu and create a new bucket that will hold all the product images.

Appwrite 9

Under the Settings tab, update the Permissions to allow any user for now.

Appwrite 10

Set the acceptable file formats. Since we are uploading images, you can select the .jpg and .png file formats.

Appwrite 11

Finally, copy your bucket ID into .env.local file.

NEXT_PUBLIC_BUCKET_ID=<YOUR_BUCKET_ID>
Enter fullscreen mode Exit fullscreen mode

Congratulations! You've successfully configured Appwrite. We can now start interacting with its various features.


How to perform CRUD operations with Appwrite

In this section, you'll learn how to create, retrieve, and delete products from Appwrite. Users need to be able to view existing products before making a purchase, while Admin users should have the permission to add and delete products from the application.

First, create a utils.ts file within the Next.js app folder. This file will contain all Appwrite database interactions, which you can then import into the necessary pages.

cd app
touch utils.ts
Enter fullscreen mode Exit fullscreen mode

Saving products to Appwrite

Recall that the products collection has three attributes: name, image, and price. Therefore, when adding products to the database, you need to first upload the product's image, retrieve its URL and ID from the response, and then upload the URL as the product's image attribute, using the image's storage ID for the product data.

Here is the code snippet that explains this:

import { db, storage } from "@/app/appwrite";
import { ID } from "appwrite";

export const createProduct = async (
    productTitle: string,
    productPrice: number,
    productImage: any
) => {
    try {
        //👇🏻 upload the image
        const response = await storage.createFile(
            process.env.NEXT_PUBLIC_BUCKET_ID!,
            ID.unique(),
            productImage
        );
        //👇🏻 get the image's URL
        const file_url = `https://cloud.appwrite.io/v1/storage/buckets/${process.env.NEXT_PUBLIC_BUCKET_ID}/files/${response.$id}/view?project=${process.env.NEXT_PUBLIC_PROJECT_ID}&mode=admin`;

        //👇🏻 add the product to the database
        await db.createDocument(
            process.env.NEXT_PUBLIC_DB_ID!,
            process.env.NEXT_PUBLIC_PRODUCTS_COLLECTION_ID!,
            response.$id, //👉🏻 use the image's ID
            {
                name: productTitle,
                price: productPrice,
                image: file_url,
            }
        );
        alert("Product created successfully");
    } catch (err) {
        console.error(err);
    }
};
Enter fullscreen mode Exit fullscreen mode

The code snippet above uploads the image to Appwrite's cloud storage and retrieves the exact image URL using the bucket ID, image ID, and project ID. Once the image is successfully uploaded, its ID is used in the product's data to enable easy retrieval and reference.

Retrieving products from Appwrite

To fetch the products from Appwrite, you can execute the function below within the React useEffect hook when the page loads.

export const fetchProducts = async () => {
    try {
        const products = await db.listDocuments(
            process.env.NEXT_PUBLIC_DB_ID!,
            process.env.NEXT_PUBLIC_PRODUCTS_COLLECTION_ID!
        );
        if (products.documents) {
            return products.documents;
        }
    } catch (err) {
        console.error(err);
    }
};
Enter fullscreen mode Exit fullscreen mode

The fetchProducts function returns all the data within the products collection.

Deleting products from Appwrite

Admin users can also delete a product via its ID. The deleteProduct function accepts the product's ID as a parameter and deletes the selected product from the database, including its image, since they use the same ID attribute.

export const deleteProduct = async (id: string) => {
    try {
        await db.deleteDocument(
            process.env.NEXT_PUBLIC_DB_ID!,
            process.env.NEXT_PUBLIC_PRODUCTS_COLLECTION_ID!,
            id
        );
        await storage.deleteFile(process.env.NEXT_PUBLIC_BUCKET_ID!, id);

        alert("Product deleted successfully");
    } catch (err) {
        console.error(err);
    }
};
Enter fullscreen mode Exit fullscreen mode

How to authenticate users with Appwrite

In the previous sections, we've configured the GitHub authentication method. Here, you'll learn how to handle user sign-ins into the application.

To enable customers to sign into the application using their GitHub account, execute the function below when they click the Sign in button. The function redirects the user to GitHub, where they can authorize or grant permission to the application and then sign into the application:


import { account } from "../appwrite";
import { OAuthProvider } from "appwrite";

const handleGoogleSignIn = async () => {
    try {
        account.createOAuth2Session(
            OAuthProvider.Github,
            "http://localhost:3000",
            "http://localhost:3000/login"
        );
    } catch (err) {
        console.error(err);
    }
};
Enter fullscreen mode Exit fullscreen mode

Admin users can sign into the application using an email and password. Appwrite validates the credentials before granting access to the application's dashboard.

import { account } from "@/app/appwrite";

const handleLogin = async (e: React.FormEvent) => {
    e.preventDefault();
    try {
        await account.createEmailPasswordSession(email, password);
        alert(`Welcome back 🎉`);
        router.push("/admin/dashboard");
    } catch (err) {
        console.error(err);
        alert("Invalid credentials ❌");
    }
};
Enter fullscreen mode Exit fullscreen mode

Appwrite also allows you to fetch the current user's data. For instance, if only authenticated users can make payments, you can do this by running the code snippet below. It retrieves the current user's data or returns null if the user is not logged in.

import { account } from "@/app/appwrite";

useEffect(() => {
    const checkAuthStatus = async () => {
        try {
            const request = await account.get();
            setUser(request);
        } catch (err) {
            console.log(err);
        }
    };
    checkAuthStatus();
}, []);
Enter fullscreen mode Exit fullscreen mode

How to add Stripe payment checkout to Next.js

In this section, you'll learn how to implement a Stripe payment checkout in the application. Stripe is a popular online payment processing platform that enables you to create products and integrate both one-time and recurring payment methods into your application.

First, you need to create a Stripe account. You can use a test mode account for this tutorial.

Stripe 1

Click on Developers from the top menu and copy your secret key from the API keys menu.

Stripe 2

Paste your Stripe secret key into the .env.local file.

STRIPE_SECRET_KEY=<your_secret_key>
Enter fullscreen mode Exit fullscreen mode

Install the Stripe Node.js SDK.

npm install stripe
Enter fullscreen mode Exit fullscreen mode

Next, create an api folder within the Next.js app folder. The api folder will contain all the API routes and endpoints for the application.

cd app
mkdir api
Enter fullscreen mode Exit fullscreen mode

Create a checkout endpoint by adding a checkout folder within the api folder.

cd api
mkdir checkout && cd checkout
touch route.ts
Enter fullscreen mode Exit fullscreen mode

Copy the code snippet below into the route.ts file.

import { NextRequest, NextResponse } from "next/server";
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

export async function POST(req: NextRequest) {
    //👇🏻 accepts the customer's cart
    const cart = await req.json();

    try {
        //👇🏻 creates a checkout session
        const session = await stripe.checkout.sessions.create({
            payment_method_types: ["card"],
            line_items: cart.map((product: Product) => ({
                price_data: {
                    currency: "usd",
                    product_data: {
                        name: product.name,
                    },
                    unit_amount: product.price * 100,
                },
                quantity: 1,
            })),
            mode: "payment",
            cancel_url: `http://localhost:3000/?canceled=true`,
            success_url: `http://localhost:3000?success=true&session_id={CHECKOUT_SESSION_ID}`,
        });
        //👇🏻 return the session URL
        return NextResponse.json({ session: session.url }, { status: 200 });
    } catch (err) {
        return NextResponse.json({ err }, { status: 500 });
    }
}
Enter fullscreen mode Exit fullscreen mode

The code snippet above creates a checkout endpoint that accepts POST requests. It creates a checkout session for the customer and returns the session URL.

The cancel_url and success_url determine where to redirect the user after completing or canceling a payment.

Finally, you can send a customer's cart to the /checkout endpoint when a user decides to make payment for products by running the code snippet below:

const processPayment = async (cart: Product[]) => {
    try {
        if (user !== null) {
            //👇🏻 saves cart to local storage
            localStorage.setItem("cart", JSON.stringify(cart));
            //👇🏻 sends cart to /checkout route
            const request = await fetch("/api/checkout", {
                method: "POST",
                body: JSON.stringify(cart),
                headers: { "Content-Type": "application/json" },
            });
            //👇🏻 retrieves the session URL
            const { session } = await request.json();
            //👇🏻 redirects the user to the checkout page
            window.location.assign(session);
        } else {
            //👇🏻 redirects unauthenticated users
            router.push("/login");
        }
    } catch (err) {
        console.error(err);
    }
};
Enter fullscreen mode Exit fullscreen mode

The code snippet above saves the cart to the browser's local storage and sends it to the API endpoint, then retrieves the response (session URL) from the backend server and redirects the user to the Stripe checkout page.

Stripe 3


Sending in-app and email notifications with Novu

Novu is the first notification infrastructure that provides a unified API for sending notifications through multiple channels, including In-App, Push, Email, SMS, and Chat.

In this section, you'll learn how to add Novu to your application to enable you to send email and in-app messages.

First, install the required Novu packages:

npm install @novu/node @novu/echo @novu/notification-center
Enter fullscreen mode Exit fullscreen mode

When users make a purchase, they will receive a payment confirmation email, and the admin user also receives an in-app notification.

To do this, you need to create an account on Novu and set up a primary email provider. We'll use Resend for this tutorial.

After creating an account on Novu, create a Resend account, and select API Keys from the sidebar menu on your dashboard to create one.

Resend 1

Next, return to your Novu dashboard, select Integrations Store from the sidebar menu, and add Resend as an email provider. You'll need to paste your Resend API key and email address into the required fields.

Novu 1

Select Settings from the sidebar menu and copy your Novu APIkey and App ID into a .env.local file as shown below. Also, copy your subscriber ID into its field - you can get this from the Subscribers section.

NOVU_API_KEY=<YOUR_API_FOR_NEXT_SERVER>
NEXT_PUBLIC_NOVU_API_KEY=<YOUR_API_FOR_NEXT_CLIENT>

NEXT_PUBLIC_NOVU_APP_ID=<YOUR_API_ID>

NOVU_SUBSCRIBER_ID=<YOUR_API_FOR_NEXT_SERVER>
NEXT_PUBLIC_NOVU_SUBSCRIBER_ID=<YOUR_API_FOR_CLIENT>
Enter fullscreen mode Exit fullscreen mode

Novu 2

Finally, add the Novu notification bell to the Admin dashboard to enable admin users to receive notifications within the application.

import {
    NovuProvider,
    PopoverNotificationCenter,
    NotificationBell,
} from "@novu/notification-center";

export default function AdminNav() {
    return (
        <NovuProvider
            subscriberId={process.env.NEXT_PUBLIC_NOVU_SUBSCRIBER_ID!}
            applicationIdentifier={process.env.NEXT_PUBLIC_NOVU_APP_ID!}
        >
            <PopoverNotificationCenter colorScheme='light'>
                {({ unseenCount }) => <NotificationBell unseenCount={unseenCount} />}
            </PopoverNotificationCenter>
        </NovuProvider>
    );
}
Enter fullscreen mode Exit fullscreen mode

Dashboard


How to create notification workflows with Novu Echo

Novu offers a code-first workflow engine that enables you to create notification workflows within your codebase. It allows you to integrate email, SMS, and chat template and content generators, such as React Email and MJML, into Novu to create advanced and powerful notifications.

In this section, you'll learn how to create notification workflows within your application, use email notification templates with Novu, and send in-app and email notifications with Novu.

Install React Email by running the following command:

npm install react-email @react-email/components -E
Enter fullscreen mode Exit fullscreen mode

Include the following script in your package.json file. The --dir flag gives React Email access to the email templates located within the project. In this case, the email templates are located in the src/emails folder.

{
    "scripts": {
        "email": "email dev --dir src/emails"
    }
}
Enter fullscreen mode Exit fullscreen mode

Next, create an emails folder containing an email.tsx within the Next.js app folder and copy the code snippet below into the file:

import {
    Body,
    Column,
    Container,
    Head,
    Heading,
    Hr,
    Html,
    Link,
    Preview,
    Section,
    Text,
    Row,
    render,
} from "@react-email/components";
import * as React from "react";

const EmailTemplate = ({
    message,
    subject,
    name,
}: {
    message: string;
    subject: string;
    name: string;
}) => (
    <Html>
        <Head />
        <Preview>{subject}</Preview>
        <Body style={main}>
            <Container style={container}>
                <Section style={header}>
                    <Row>
                        <Column style={headerContent}>
                            <Heading style={headerContentTitle}>{subject}</Heading>
                        </Column>
                    </Row>
                </Section>

                <Section style={content}>
                    <Text style={paragraph}>Hey {name},</Text>
                    <Text style={paragraph}>{message}</Text>
                </Section>
            </Container>

            <Section style={footer}>
                <Text style={footerText}>
                    You&apos;re receiving this email because your subscribed to Newsletter
                    App
                </Text>

                <Hr style={footerDivider} />
                <Text style={footerAddress}>
                    <strong>Novu Store</strong>, &copy;{" "}
                    <Link href='https://novu.co'>Novu</Link>
                </Text>
            </Section>
        </Body>
    </Html>
);

export function renderEmail(inputs: {
    message: string;
    subject: string;
    name: string;
}) {
    return render(<EmailTemplate {...inputs} />);
}

const main = {
    backgroundColor: "#f3f3f5",
    fontFamily: "HelveticaNeue,Helvetica,Arial,sans-serif",
};

const headerContent = { padding: "20px 30px 15px" };

const headerContentTitle = {
    color: "#fff",
    fontSize: "27px",
    fontWeight: "bold",
    lineHeight: "27px",
};

const paragraph = {
    fontSize: "15px",
    lineHeight: "21px",
    color: "#3c3f44",
};

const divider = {
    margin: "30px 0",
};

const container = {
    width: "680px",
    maxWidth: "100%",
    margin: "0 auto",
    backgroundColor: "#ffffff",
};

const footer = {
    width: "680px",
    maxWidth: "100%",
    margin: "32px auto 0 auto",
    padding: "0 30px",
};

const content = {
    padding: "30px 30px 40px 30px",
};

const header = {
    borderRadius: "5px 5px 0 0",
    display: "flex",
    flexDireciont: "column",
    backgroundColor: "#2b2d6e",
};

const footerDivider = {
    ...divider,
    borderColor: "#d6d8db",
};

const footerText = {
    fontSize: "12px",
    lineHeight: "15px",
    color: "#9199a1",
    margin: "0",
};

const footerLink = {
    display: "inline-block",
    color: "#9199a1",
    textDecoration: "underline",
    fontSize: "12px",
    marginRight: "10px",
    marginBottom: "0",
    marginTop: "8px",
};

const footerAddress = {
    margin: "4px 0",
    fontSize: "12px",
    lineHeight: "15px",
    color: "#9199a1",
};
Enter fullscreen mode Exit fullscreen mode

The code snippet above creates an customizable email template using React Email. You can find more easy-to-edit inspirations or templates. The component also accepts a message, subject, and name as props, and fills them into the elements.

Finally, you can run npm run email in your terminal to preview the template.

Next, let's integrate the email template to Novu Echo. First, close the React Email server, and run the code snippet below. It opens the Novu Dev Studio in your browser.

npx novu-labs@latest echo
Enter fullscreen mode Exit fullscreen mode

Create an echo folder containing a client.ts file within the Next.js app folder and copy this code snippet into the file.

import { Echo } from "@novu/echo";
import { renderEmail } from "@/app/emails/email";

interface EchoProps {
    step: any;
    payload: {
        subject: string;
        message: string;
        name: string;
        totalAmount: string;
    };
}
export const echo = new Echo({
    apiKey: process.env.NEXT_PUBLIC_NOVU_API_KEY!,
    devModeBypassAuthentication: process.env.NODE_ENV === "development",
});

echo.workflow(
    "novu-store",
    async ({ step, payload }: EchoProps) => {
        //👇🏻 in-app notification step
        await step.inApp("notify-admin", async () => {
            return {
                body: `${payload.name} just made a new purchase of ${payload.totalAmount} 🎉`,
            };
        });
        //👇🏻 email notification step
        await step.email(
            "email-customer",
            async () => {
                return {
                    subject: `${payload ? payload?.subject : "No Subject"}`,
                    body: renderEmail(payload),
                };
            },
            {
                inputSchema: {
                    type: "object",
                    properties: {},
                },
            }
        );
    },
    {
        payloadSchema: {
            type: "object",
            properties: {
                message: {
                    type: "string",
                    default: "Congratulations! Your purchase was successful! 🎉",
                },
                subject: { type: "string", default: "Message from Novu Store" },
                name: { type: "string", default: "User" },
                totalAmount: { type: "string", default: "0" },
            },
            required: ["message", "subject", "name", "totalAmount"],
            additionalProperties: false,
        },
    }
);
Enter fullscreen mode Exit fullscreen mode

The code snippet defines a Novu notification workflow named novu-store, which accepts a payload containing the email subject, message, the customer's name and the total amount.

The workflow has two steps: in-app and email notification. The in-app notification sends a message to the Admin using the notification bell and the email sends a message to the customer’s email.

Next, you need to create an API route for Novu Echo. Within the api folder, create an email folder containing a route.ts file and copy the provided code snippet below into the file.

import { serve } from "@novu/echo/next";
import { echo } from "@/app/echo/client";

export const { GET, POST, PUT } = serve({ client: echo });
Enter fullscreen mode Exit fullscreen mode

Run npx novu-labs@latest echo in your terminal. It will automatically open the Novu Dev Studio where you can preview your workflow and Sync it with the Cloud.

Novu 3

The Sync to Cloud button triggers a pop-up that provides instructions on how to push your workflow to the Novu Cloud.

Novu 4

To proceed, run the following code snippet in your terminal. This will generate a unique URL representing a local tunnel between your development environment and the cloud environment.

npx localtunnel --port 3000
Enter fullscreen mode Exit fullscreen mode

Copy the generated link along with your Echo API endpoint into the Echo Endpoint field, click the Create Diff button, and deploy the changes.

https://<LOCAL_TUNNEL_URL>/<ECHO_API_ENDPOINT (/api/email)> 
Enter fullscreen mode Exit fullscreen mode

Congratulations! You've just created a Novu workflow from your codebase.

Novu 5

Finally, let's create the endpoint that sends the email and in-app notifications when a user makes a payment. Create an api/send route and copy the code snippet below into the file:

import { NextRequest, NextResponse } from "next/server";
import { Novu } from "@novu/node";

const novu = new Novu(process.env.NOVU_API_KEY!);

export async function POST(req: NextRequest) {
    const { email, name, totalAmount } = await req.json();

    const { data } = await novu.trigger("novu-store", {
        to: {
            subscriberId: process.env.NOVU_SUBSCRIBER_ID!,
            email,
            firstName: name,
        },
        payload: {
            name,
            totalAmount,
            subject: `Purchase Notification from Novu Store`,
            message: `Your purchase of ${totalAmount} was successful! 🎉`,
        },
    });
    console.log(data.data);

    return NextResponse.json(
        {
            message: "Purchase Completed!",
            data: { novu: data.data },
            success: true,
        },
        { status: 200 }
    );
}
Enter fullscreen mode Exit fullscreen mode

The endpoint accepts the customer's email, name, and total amount paid, and triggers the Novu notification workflow to send the required notifications after a payment is successful.


Conclusion

So far, you've learned how to do the following:

  • Implement multiple authentication methods, store, and retrieve data and files from Appwrite.
  • Create email templates with React Email, and send in-app and email notifications with Novu.

If you are looking forward to sending notifications within your applications, Novu is your best choice. With Novu, you can add multiple notification channels to your applications, including chat, SMS, email, push, and in-app notifications.

The source code for this tutorial is available here:
https://github.com/novuhq/ecom-store-with-nextjs-appwrite-novu-and-stripe

Thank you for reading!

Top comments (15)

Collapse
 
tpoisson805 profile image
Tim Poisson

Just curious why you would name your login function handleGoogleSignIn() when you are using GitHub auth? Would it not make more sense to use a name that includes GitHub for better clarity?

Collapse
 
empe profile image
Emil Pearce

You are absolutely right! @tpoisson805

I've used this auth function with Google in another project and forgot to change the name. 😅

Collapse
 
devyaz profile image
devyaz

So what is Novu? What can you do with Novu, Novu vs Appwrite, whats the difference/similarities

Collapse
 
empe profile image
Emil Pearce

Hey @devyaz!

Both Novu and Appwrite are powerful tools, but they serve different primary purposes.

Appwrite is a backend-as-a-service (BaaS) platform that provides a set of building blocks for web, mobile, and Flutter applications. (Like Firebase)

Novu is specifically designed for notification management, focusing on delivering messages to users across multiple channels.

Using both platforms together can be beneficial: Appwrite can handle user management and other backend tasks, while Novu manages notifications.

Similarities

  • Open-source: Both platforms are open-source, offering flexibility and customization.
  • API-driven: They both provide APIs for interacting with their features.

Differences

  • Core functionality: Appwrite is a comprehensive backend platform, while Novu specializes in notifications.
  • Features: Appwrite offers a broad range of features for building backend infrastructure, while Novu focuses on notification-related functionalities.

To put it simply, integrating Novu for notifications is like integrating Stripe for payments. With Stripe, you avoid the complexities of processing and storing users' credit card information.

Similarly, with Novu, you avoid the hassle of building and maintaining notification workflows, user preferences, and integrations with providers like SMS, Email, WhatsApp, and Slack. Just add Novu, and you're set!

I hope it makes more sense now..

Collapse
 
devyaz profile image
devyaz

So much! Thank you! Ill look into Novu

Thread Thread
 
devyaz profile image
devyaz

Last question, is Novu only for react/NextJs only? Cant we use plain typescript or Javascript with Express?

Collapse
 
dhruv_verma_e344c639e7538 profile image
Dhruv Verma

I would like to add you can replace localhost to constant APP_URL based on production and development environment.

Collapse
 
nevodavid profile image
Nevo David

Novu Echo is fantastic!
I wonder how good is the typing once you use the client :)

Collapse
 
empe profile image
Emil Pearce

You can always test it out! Hehe

Collapse
 
devyaz profile image
devyaz

this is cool am sharing

Collapse
 
empe profile image
Emil Pearce

Thank you!

Collapse
 
unicodeveloper profile image
Prosper Otemuyiwa

Thanks for teaching me how to build an ecommerce store with ease!

Collapse
 
empe profile image
Emil Pearce

Ahaha thanks!

Collapse
 
gal_tidhar_e93f7847d6ce6a profile image
Gal Tidhar

Awesome, will share

Collapse
 
empe profile image
Emil Pearce

Thank you very much Gal!