<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Abdur Rakib Rony</title>
    <description>The latest articles on DEV Community by Abdur Rakib Rony (@abdur_rakibrony_97cea0e9).</description>
    <link>https://dev.to/abdur_rakibrony_97cea0e9</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1588222%2F84292356-f3b8-40d3-aefd-09e0f23cc721.png</url>
      <title>DEV Community: Abdur Rakib Rony</title>
      <link>https://dev.to/abdur_rakibrony_97cea0e9</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/abdur_rakibrony_97cea0e9"/>
    <language>en</language>
    <item>
      <title>Building a Full-Stack User Management System with Next.js 14, GraphQL, Prisma, and PostgreSQL</title>
      <dc:creator>Abdur Rakib Rony</dc:creator>
      <pubDate>Sun, 10 Nov 2024 13:07:36 +0000</pubDate>
      <link>https://dev.to/abdur_rakibrony_97cea0e9/building-a-full-stack-user-management-system-with-nextjs-14-graphql-prisma-and-postgresql-34ma</link>
      <guid>https://dev.to/abdur_rakibrony_97cea0e9/building-a-full-stack-user-management-system-with-nextjs-14-graphql-prisma-and-postgresql-34ma</guid>
      <description>&lt;p&gt;In this comprehensive guide, we'll build a complete user management system using modern web technologies. You can find the complete source code in my GitHub repository.&lt;br&gt;
&lt;strong&gt;Prerequisites&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node.js installed on your machine&lt;/li&gt;
&lt;li&gt;PostgreSQL database&lt;/li&gt;
&lt;li&gt;Basic knowledge of React and Next.js&lt;/li&gt;
&lt;li&gt;Understanding of GraphQL concepts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Project Setup&lt;/strong&gt;&lt;br&gt;
First, create a new Next.js project and install the required dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx create-next-app@latest graphql-user-management
cd graphql-user-management

npm install @apollo/server @as-integrations/next @prisma/client graphql-tag lucide-react
npm install -D prisma tailwindcss postcss
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Project Structure&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;├── app/
│   ├── actions/
│   │   └── userActions.js
│   ├── api/
│   │   └── graphql/
│   │       └── route.js
│   ├── components/
│   │   └── UserManagement.jsx
│   ├── graphql/
│   │   └── schema.js
│   ├── lib/
│   │   └── prisma.js
│   ├── page.js
│   └── layout.js
├── prisma/
│   └── schema.prisma
└── package.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Database Setup with Prisma&lt;/strong&gt;&lt;br&gt;
First, let's set up our database schema using Prisma. Create a new file &lt;code&gt;prisma/schema.prisma:&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id        Int      @id @default(autoincrement())
  name      String
  email     String   @unique
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;GraphQL Schema&lt;/strong&gt;&lt;br&gt;
Create &lt;code&gt;app/graphql/schema.js&lt;/code&gt;to define our GraphQL types and operations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { gql } from "graphql-tag";

export const typeDefs = gql`
  type User {
    id: ID!
    name: String!
    email: String!
    createdAt: String!
    updatedAt: String!
  }

  type Query {
    users: [User!]!
    user(id: ID!): User
  }

  type Mutation {
    createUser(name: String!, email: String!): User!
    updateUser(id: ID!, name: String, email: String): User!
    deleteUser(id: ID!): Boolean!
  }
`;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Prisma Client Setup&lt;/strong&gt;&lt;br&gt;
Create &lt;code&gt;app/lib/prisma.js&lt;/code&gt; to initialize the Prisma client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

export default prisma
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;GraphQL API Route&lt;/strong&gt;&lt;br&gt;
Create &lt;code&gt;app/api/graphql/route.js&lt;/code&gt; to set up Apollo Server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { ApolloServer } from "@apollo/server";
import { startServerAndCreateNextHandler } from "@as-integrations/next";
import { typeDefs } from "@/graphql/schema";
import prisma from "@/lib/prisma";

const resolvers = {
  Query: {
    users: async () =&amp;gt; {
      const users = await prisma.user.findMany({
        orderBy: {
          createdAt: "desc",
        },
      });
      return users;
    },
    user: async (_, { id }) =&amp;gt; {
      const user = await prisma.user.findUnique({
        where: {
          id: parseInt(id),
        },
      });
      return user;
    },
  },
  Mutation: {
    createUser: async (_, { name, email }) =&amp;gt; {
      try {
        const user = await prisma.user.create({
          data: {
            name,
            email,
          },
        });
        return user;
      } catch (error) {
        if (error.code === "P2002") {
          throw new Error("A user with this email already exists");
        }
        throw error;
      }
    },
    updateUser: async (_, { id, name, email }) =&amp;gt; {
      try {
        const user = await prisma.user.update({
          where: {
            id: parseInt(id),
          },
          data: {
            name,
            email,
          },
        });
        return user;
      } catch (error) {
        if (error.code === "P2002") {
          throw new Error("A user with this email already exists");
        }
        throw error;
      }
    },
    deleteUser: async (_, { id }) =&amp;gt; {
      try {
        await prisma.user.delete({
          where: {
            id: parseInt(id),
          },
        });
        return true;
      } catch (error) {
        return false;
      }
    },
  },
};

const server = new ApolloServer({
  typeDefs,
  resolvers,
});

const handler = startServerAndCreateNextHandler(server);
export { handler as GET, handler as POST };
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Server Actions&lt;/strong&gt;&lt;br&gt;
Create &lt;code&gt;app/actions/userActions.js&lt;/code&gt; to handle server-side mutations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"use server";

import { revalidatePath } from "next/cache";

async function fetchGraphQL(query, variables = {}) {
  try {
    const response = await fetch("http://localhost:3000/api/graphql", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ query, variables }),
      cache: "no-store",
      next: { 
        tags: ["users"],
        revalidate: 0 
      }
    });

    const result = await response.json();

    if (result.errors) {
      throw new Error(result.errors[0].message);
    }

    return result;
  } catch (error) {
    throw new Error(error.message || "An error occurred");
  }
}

export async function getUsers() {
  try {
    const { data } = await fetchGraphQL(`
      query GetUsers {
        users {
          id
          name
          email
          createdAt
          updatedAt
        }
      }
    `);
    return { users: data.users };
  } catch (error) {
    return { error: error.message };
  }
}

export async function createUser(name, email) {
  try {
    const { data } = await fetchGraphQL(
      `
      mutation CreateUser($name: String!, $email: String!) {
        createUser(name: $name, email: $email) {
          id
          name
          email
        }
      }
    `,
      { name, email }
    );

    revalidatePath("/");
    return { success: true, user: data.createUser };
  } catch (error) {
    return { error: error.message };
  }
}

export async function updateUser(id, name, email) {
  try {
    const { data } = await fetchGraphQL(
      `
      mutation UpdateUser($id: ID!, $name: String!, $email: String!) {
        updateUser(id: $id, name: $name, email: $email) {
          id
          name
          email
        }
      }
    `,
      { id, name, email }
    );

    revalidatePath("/");
    return { success: true, user: data.updateUser };
  } catch (error) {
    return { error: error.message };
  }
}

export async function deleteUser(id) {
  try {
    await fetchGraphQL(
      `
      mutation DeleteUser($id: ID!) {
        deleteUser(id: $id)
      }
    `,
      { id }
    );

    revalidatePath("/");
    return { success: true };
  } catch (error) {
    return { error: error.message };
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;User Management Component&lt;/strong&gt;&lt;br&gt;
Create &lt;code&gt;app/components/UserManagement.jsx&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"use client";

import React, { useState, useTransition } from "react";
import { Pencil, Trash2 } from "lucide-react";
import { createUser, updateUser, deleteUser } from '@/app/actions/userActions';

function UserManagement({ users = [] }) {
  const [isPending, startTransition] = useTransition();
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");
  const [editingUser, setEditingUser] = useState(null);
  const [error, setError] = useState(null);

  const handleCreateUser = async (e) =&amp;gt; {
    e.preventDefault();
    setError(null);

    startTransition(async () =&amp;gt; {
      try {
        const result = await createUser(name, email);
        if (result.error) {
          setError(result.error);
        } else {
          setName("");
          setEmail("");
        }
      } catch (err) {
        setError(err.message);
      }
    });
  };

  const handleUpdateUser = async (e) =&amp;gt; {
    e.preventDefault();
    setError(null);

    startTransition(async () =&amp;gt; {
      try {
        const result = await updateUser(editingUser.id, name, email);
        if (result.error) {
          setError(result.error);
        } else {
          setEditingUser(null);
          setName("");
          setEmail("");
        }
      } catch (err) {
        setError(err.message);
      }
    });
  };

  const handleDeleteUser = async (id) =&amp;gt; {
    setError(null);

    startTransition(async () =&amp;gt; {
      try {
        const result = await deleteUser(id);
        if (result.error) {
          setError(result.error);
        }
      } catch (err) {
        setError(err.message);
      }
    });
  };

  return (
    &amp;lt;div className="bg-white rounded-lg shadow-md w-full max-w-4xl mx-auto mt-8"&amp;gt;
      &amp;lt;div className="p-6 border-b border-gray-200"&amp;gt;
        &amp;lt;h2 className="text-xl font-semibold"&amp;gt;User Management&amp;lt;/h2&amp;gt;
      &amp;lt;/div&amp;gt;

      &amp;lt;div className="p-6"&amp;gt;
        {error &amp;amp;&amp;amp; (
          &amp;lt;div className="mb-4 p-4 bg-red-50 border border-red-200 text-red-600 rounded-md"&amp;gt;
            {error}
          &amp;lt;/div&amp;gt;
        )}

        &amp;lt;form
          onSubmit={editingUser ? handleUpdateUser : handleCreateUser}
          className="space-y-4 mb-8"
        &amp;gt;
          &amp;lt;input
            type="text"
            placeholder="Name"
            value={name}
            onChange={(e) =&amp;gt; setName(e.target.value)}
            className="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100"
            disabled={isPending}
            required
          /&amp;gt;
          &amp;lt;input
            type="email"
            placeholder="Email"
            value={email}
            onChange={(e) =&amp;gt; setEmail(e.target.value)}
            className="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100"
            disabled={isPending}
            required
          /&amp;gt;
          &amp;lt;button
            type="submit"
            className={`w-full py-2 px-4 rounded-md text-white font-medium 
              ${isPending ? "bg-blue-400 cursor-not-allowed" : "bg-blue-500 hover:bg-blue-600"}`}
            disabled={isPending}
          &amp;gt;
            {isPending ? (
              &amp;lt;svg className="animate-spin h-5 w-5 mx-auto" viewBox="0 0 24 24"&amp;gt;
                &amp;lt;circle
                  className="opacity-25"
                  cx="12"
                  cy="12"
                  r="10"
                  stroke="currentColor"
                  strokeWidth="4"
                  fill="none"
                /&amp;gt;
                &amp;lt;path
                  className="opacity-75"
                  fill="currentColor"
                  d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
                /&amp;gt;
              &amp;lt;/svg&amp;gt;
            ) : editingUser ? (
              "Update User"
            ) : (
              "Create User"
            )}
          &amp;lt;/button&amp;gt;

          {editingUser &amp;amp;&amp;amp; (
            &amp;lt;button
              type="button"
              onClick={() =&amp;gt; {
                setEditingUser(null);
                setName("");
                setEmail("");
              }}
              className="w-full py-2 px-4 border border-gray-300 rounded-md text-gray-700 font-medium hover:bg-gray-50 disabled:bg-gray-100"
              disabled={isPending}
            &amp;gt;
              Cancel Edit
            &amp;lt;/button&amp;gt;
          )}
        &amp;lt;/form&amp;gt;

        {isPending &amp;amp;&amp;amp; (
          &amp;lt;div className="fixed top-4 right-4 bg-blue-500 text-white px-4 py-2 rounded-md shadow-lg"&amp;gt;
            Saving changes...
          &amp;lt;/div&amp;gt;
        )}

        &amp;lt;div className="space-y-4"&amp;gt;
          {users.map((user) =&amp;gt; (
            &amp;lt;div
              key={user.id}
              className="flex items-center justify-between p-4 border border-gray-200 rounded-md"
            &amp;gt;
              &amp;lt;div&amp;gt;
                &amp;lt;h3 className="font-medium"&amp;gt;{user.name}&amp;lt;/h3&amp;gt;
                &amp;lt;p className="text-sm text-gray-500"&amp;gt;{user.email}&amp;lt;/p&amp;gt;
              &amp;lt;/div&amp;gt;
              &amp;lt;div className="flex space-x-2"&amp;gt;
                &amp;lt;button
                  onClick={() =&amp;gt; {
                    setEditingUser(user);
                    setName(user.name);
                    setEmail(user.email);
                  }}
                  disabled={isPending}
                  className="p-2 text-gray-600 hover:text-blue-600 hover:bg-blue-50 rounded-md disabled:opacity-50"
                &amp;gt;
                  &amp;lt;Pencil className="h-4 w-4" /&amp;gt;
                &amp;lt;/button&amp;gt;
                &amp;lt;button
                  onClick={() =&amp;gt; handleDeleteUser(user.id)}
                  disabled={isPending}
                  className="p-2 text-gray-600 hover:text-red-600 hover:bg-red-50 rounded-md disabled:opacity-50"
                &amp;gt;
                  &amp;lt;Trash2 className="h-4 w-4" /&amp;gt;
                &amp;lt;/button&amp;gt;
              &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
          ))}
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default UserManagement;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Root Page Component&lt;/strong&gt;&lt;br&gt;
Create &lt;code&gt;app/page.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import UserManagement from "./components/UserManagement";
import { getUsers } from "./actions/userActions";

export const dynamic = "force-dynamic";
export const revalidate = 0;

export default async function HomePage() {
  const { users, error } = await getUsers();

  if (error) {
    return &amp;lt;div&amp;gt;Error loading users: {error}&amp;lt;/div&amp;gt;;
  }

  return &amp;lt;UserManagement users={users} /&amp;gt;;
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Setting Up Environment Variables&lt;/strong&gt;&lt;br&gt;
Create a &lt;code&gt;.env&lt;/code&gt; file in your root directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DATABASE_URL="postgresql://username:password@localhost:5432/your_database_name"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace the values with your actual PostgreSQL database credentials.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Package.json Configuration&lt;/strong&gt;&lt;br&gt;
Here's the complete &lt;code&gt;package.json&lt;/code&gt; configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "name": "graphql",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@apollo/server": "^4.11.2",
    "@as-integrations/next": "^3.2.0",
    "@prisma/client": "^5.22.0",
    "graphql-tag": "^2.12.6",
    "lucide-react": "^0.456.0",
    "next": "15.0.3",
    "react": "19.0.0-rc-66855b96-20241106",
    "react-dom": "19.0.0-rc-66855b96-20241106"
  },
  "devDependencies": {
    "eslint": "^8",
    "eslint-config-next": "15.0.3",
    "postcss": "^8",
    "prisma": "^5.22.0",
    "tailwindcss": "^3.4.1"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Initialize Prisma&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx prisma generate
npx prisma db push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Go to code&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://github.com/abdur-rakib-rony/nextjs-graphql-postgres-prisma-crud-operation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Happy coding! 🚀&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>graphql</category>
      <category>postgres</category>
      <category>prisma</category>
    </item>
    <item>
      <title>Implementing Secure Authentication in Next.js with JWT and MongoDB. Protect Routes using middleware</title>
      <dc:creator>Abdur Rakib Rony</dc:creator>
      <pubDate>Fri, 05 Jul 2024 16:28:37 +0000</pubDate>
      <link>https://dev.to/abdur_rakibrony_97cea0e9/implementing-secure-authentication-in-nextjs-with-jwt-and-mongodb-protect-routes-using-middleware-4389</link>
      <guid>https://dev.to/abdur_rakibrony_97cea0e9/implementing-secure-authentication-in-nextjs-with-jwt-and-mongodb-protect-routes-using-middleware-4389</guid>
      <description>&lt;p&gt;In modern web development, implementing a robust authentication system is crucial for securing user data and providing a seamless user experience. In this blog post, we'll explore how to implement authentication in a Next.js application using JSON Web Tokens (JWT) and MongoDB. We'll cover the key aspects of this implementation, including middleware, token management, and user registration and login.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setting Up Middleware for Route Protection&lt;/strong&gt;&lt;br&gt;
The middleware function plays a crucial role in protecting routes and ensuring that users are authenticated before accessing certain pages. Here's the implementation of the middleware function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { getToken, verifyToken } from "@/lib/auth";
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export async function middleware(request: NextRequest) {
  const token = getToken(request);
  const { pathname } = request.nextUrl;

  if (token) {
    const payload = await verifyToken(token);
    if (payload) {
      if (pathname === "/" || pathname === "/register") {
        return NextResponse.redirect(new URL("/home", request.url));
      }
      return NextResponse.next();
    }
  }

  if (pathname !== "/" &amp;amp;&amp;amp; pathname !== "/register") {
    return NextResponse.redirect(new URL("/", request.url));
  }

  return NextResponse.next();
}

export const config = {
  matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
};

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This middleware function checks if a token exists and verifies it. If the token is valid, it allows the user to proceed to the requested route. If the token is invalid or missing, it redirects the user to the login page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Managing Tokens with JSON Web Tokens (JWT)&lt;/strong&gt;&lt;br&gt;
To handle token generation and verification, we use the jose library. Below are the utility functions for managing JWTs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { jwtVerify, SignJWT } from 'jose';
import { NextRequest } from "next/server";

export function getToken(req: NextRequest) {
  return req.cookies.get("token")?.value;
}

export async function verifyToken(token: string) {
  if (!token) return null;

  try {
    const secret = new TextEncoder().encode(process.env.JWT_SECRET);
    const { payload } = await jwtVerify(token, secret);
    return payload as { userId: string };
  } catch (error) {
    console.error('Token verification failed:', error);
    return null;
  }
}

export async function createToken(payload: { userId: string }) {
  const secret = new TextEncoder().encode(process.env.JWT_SECRET!);
  const token = await new SignJWT(payload)
    .setProtectedHeader({ alg: 'HS256' })
    .setExpirationTime('1d')
    .sign(secret);
  return token;
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These functions handle retrieving the token from cookies, verifying the token, and creating a new token. The verifyToken function ensures that the token is valid and extracts the payload, while createToken generates a new token with a specified payload.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User Registration and Login&lt;/strong&gt;&lt;br&gt;
For user registration and login, we need to handle form data, hash passwords, and create tokens. Here's how we achieve this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"use server";
import { cookies } from "next/headers";
import bcrypt from "bcryptjs";
import { connectToDB } from "@/lib/db";
import User from "@/models/User";
import { loginSchema, registerSchema } from "@/zod/schema";
import { createToken } from "@/lib/auth";

export async function registerUser(formData: FormData) {
  await connectToDB();

  const parsedData = registerSchema.parse(
    Object.fromEntries(formData.entries())
  );

  const existingUser = await User.findOne({ email: parsedData.email });
  if (existingUser) {
    throw new Error("Email already exists");
  }

  const hashedPassword = await bcrypt.hash(parsedData.password, 10);

  const newUser = await User.create({
    ...parsedData,
    password: hashedPassword,
    createdAt: new Date(),
  });

  const token = await createToken({ userId: newUser._id.toString() });

  const cookieStore = cookies();
  cookieStore.set("token", token, {
    httpOnly: true,
    secure: process.env.NODE_ENV === "production",
  });
}

export async function loginUser(formData: FormData) {
  const { email, password } = loginSchema.parse(
    Object.fromEntries(formData.entries())
  );

  await connectToDB();

  const user = await User.findOne({ email });
  if (!user) {
    throw new Error("Invalid credentials");
  }

  const isPasswordValid = await bcrypt.compare(password, user.password);
  if (!isPasswordValid) {
    throw new Error("Invalid credentials");
  }

  const token = await createToken({ userId: user._id.toString() });

  const cookieStore = cookies();
  cookieStore.set("token", token, {
    httpOnly: true,
    secure: process.env.NODE_ENV === "production",
  });
}

export async function logoutUser() {
  cookies().set("token", "", { expires: new Date(0) });
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;registerUser: This function handles user registration. It connects to the database, parses the form data, checks if the email already exists, hashes the password, creates a new user, generates a token, and sets the token as an HTTP-only cookie.&lt;/p&gt;

&lt;p&gt;loginUser: This function handles user login. It parses the form data, connects to the database, verifies the user's credentials, generates a token, and sets the token as an HTTP-only cookie.&lt;/p&gt;

&lt;p&gt;logoutUser: This function handles user logout by clearing the token cookie.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;br&gt;
Implementing authentication in a Next.js application using JWT and MongoDB provides a secure and efficient way to manage user sessions. By leveraging middleware, JWT, and MongoDB, we can protect routes, verify tokens, and handle user registration and login seamlessly.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>middleware</category>
      <category>authjs</category>
      <category>security</category>
    </item>
    <item>
      <title>Building a Secure OTP-based Login System in Next.js</title>
      <dc:creator>Abdur Rakib Rony</dc:creator>
      <pubDate>Fri, 05 Jul 2024 16:22:11 +0000</pubDate>
      <link>https://dev.to/abdur_rakibrony_97cea0e9/building-a-secure-otp-based-login-system-in-nextjs-2od8</link>
      <guid>https://dev.to/abdur_rakibrony_97cea0e9/building-a-secure-otp-based-login-system-in-nextjs-2od8</guid>
      <description>&lt;p&gt;In today's digital age, ensuring the security of user authentication is paramount. One effective method is using One-Time Passwords (OTPs) for login. In this post, we'll walk through how to implement an OTP-based login system using Next.js, with both email and phone number options.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Use OTP?&lt;/strong&gt;&lt;br&gt;
OTPs add an extra layer of security by requiring a temporary code sent to the user's email or phone number. This method reduces the risk of unauthorized access, as the code is valid for a short period.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setting Up the Frontend&lt;/strong&gt;&lt;br&gt;
We start by creating a login component that captures the user's email or phone number and handles OTP sending and verification.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//login component
"use client";
import { useState, useEffect } from "react";
import { Input } from "@/components/ui/input";
import { Lock, Mail, Phone } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
  InputOTP,
  InputOTPGroup,
  InputOTPSlot,
} from "@/components/ui/input-otp";
import { SendOTP } from "@/utils/SendOTP";
import { useRouter } from "next/navigation";
import { signIn } from "next-auth/react";

const Login = () =&amp;gt; {
  const [contact, setContact] = useState("");
  const [otp, setOtp] = useState(false);
  const [otpCode, setOtpCode] = useState("");
  const [receivedOtpCode, setReceivedOtpCode] = useState("");
  const [timeLeft, setTimeLeft] = useState(60);
  const [timerRunning, setTimerRunning] = useState(false);
  const [resendClicked, setResendClicked] = useState(false);
  const [hasPassword, setHasPassword] = useState(false);
  const [password, setPassword] = useState("");
  const [isIncorrectOTP, setIsIncorrectOTP] = useState(false);

  const router = useRouter();

  const handleSendOtp = async () =&amp;gt; {
    setOtp(true);
    startTimer();
    setResendClicked(true);
    const data = await SendOTP(contact);
    if (data?.hasPassword) {
      setHasPassword(data?.hasPassword);
    }
    if (data?.otp) {
      setReceivedOtpCode(data?.otp);
    }
  };

  const handleLogin = async () =&amp;gt; {
    if (otpCode === receivedOtpCode) {
      await signIn("credentials", {
        redirect: false,
        email: isNaN(contact) ? contact : contact + "@gmail.com",
      });
      router.push("/");
    } else {
      setIsIncorrectOTP(true);
    }
  };

  const startTimer = () =&amp;gt; {
    setTimeLeft(60);
    setTimerRunning(true);
  };

  const resendOTP = () =&amp;gt; {
    setTimerRunning(false);
    startTimer();
    setResendClicked(true);
    handleSendOtp();
  };

  useEffect(() =&amp;gt; {
    let timer;
    if (timerRunning) {
      timer = setTimeout(() =&amp;gt; {
        if (timeLeft &amp;gt; 0) {
          setTimeLeft((prevTime) =&amp;gt; prevTime - 1);
        } else {
          setTimerRunning(false);
        }
      }, 1000);
    }

    return () =&amp;gt; clearTimeout(timer);
  }, [timeLeft, timerRunning]);

  useEffect(() =&amp;gt; {
    if (contact === "" || contact === null) {
      setOtp(false);
      setOtpCode("");
      setTimeLeft(60);
      setTimerRunning(false);
      setResendClicked(false);
    }
  }, [contact]);

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;div className="relative w-full max-w-sm"&amp;gt;
        {contact === "" || isNaN(contact) ? (
          &amp;lt;Mail
            className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"
            size={20}
          /&amp;gt;
        ) : (
          &amp;lt;Phone
            className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"
            size={20}
          /&amp;gt;
        )}
        &amp;lt;Input
          type="text"
          name="contact"
          value={contact}
          placeholder="Email or phone"
          onChange={(e) =&amp;gt; setContact(e.target.value)}
          disabled={contact &amp;amp;&amp;amp; otp}
          className="pl-10"
        /&amp;gt;
      &amp;lt;/div&amp;gt;
      {hasPassword ? (
        &amp;lt;div className="relative w-full max-w-sm mt-4"&amp;gt;
          &amp;lt;Lock
            className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"
            size={20}
          /&amp;gt;
          &amp;lt;Input
            type="password"
            name="password"
            value={password}
            placeholder="Password"
            onChange={(e) =&amp;gt; setPassword(e.target.value)}
            className="pl-10"
          /&amp;gt;
        &amp;lt;/div&amp;gt;
      ) : (
        &amp;lt;div&amp;gt;
          {contact &amp;amp;&amp;amp; otp &amp;amp;&amp;amp; (
            &amp;lt;div className="text-center text-green-500 text-base mt-1"&amp;gt;
              OTP sent successfully. Please enter OTP below.
            &amp;lt;/div&amp;gt;
          )}
          {contact &amp;amp;&amp;amp; otp &amp;amp;&amp;amp; (
            &amp;lt;div className="space-y-2 w-full flex flex-col items-center justify-center my-2"&amp;gt;
              &amp;lt;InputOTP
                maxLength={4}
                value={otpCode}
                onChange={(value) =&amp;gt; setOtpCode(value)}
                isError={isIncorrectOTP}
              &amp;gt;
                &amp;lt;InputOTPGroup&amp;gt;
                  &amp;lt;InputOTPSlot index={0} /&amp;gt;
                  &amp;lt;InputOTPSlot index={1} /&amp;gt;
                  &amp;lt;InputOTPSlot index={2} /&amp;gt;
                  &amp;lt;InputOTPSlot index={3} /&amp;gt;
                &amp;lt;/InputOTPGroup&amp;gt;
              &amp;lt;/InputOTP&amp;gt;
              &amp;lt;div&amp;gt;
                {resendClicked &amp;amp;&amp;amp; timeLeft &amp;gt; 0 ? (
                  &amp;lt;p className="text-sm"&amp;gt;
                    Resend OTP available in{" "}
                    &amp;lt;span className="text-blue-500"&amp;gt;
                      {timeLeft &amp;gt; 0 ? `${timeLeft}` : ""}
                    &amp;lt;/span&amp;gt;
                  &amp;lt;/p&amp;gt;
                ) : (
                  &amp;lt;Button
                    variant="link"
                    onClick={resendOTP}
                    className="text-blue-500"
                  &amp;gt;
                    Resend OTP
                  &amp;lt;/Button&amp;gt;
                )}
              &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
          )}
        &amp;lt;/div&amp;gt;
      )}
      {receivedOtpCode ? (
        &amp;lt;Button
          onClick={handleLogin}
          className="w-full mt-4 bg-green-500 hover:bg-green-400"
        &amp;gt;
          Login
        &amp;lt;/Button&amp;gt;
      ) : (
        &amp;lt;Button
          onClick={handleSendOtp}
          className="w-full mt-4 bg-green-500 hover:bg-green-400"
        &amp;gt;
          Next
        &amp;lt;/Button&amp;gt;
      )}
      {isIncorrectOTP &amp;amp;&amp;amp; (
        &amp;lt;p className="text-red-500 text-sm text-center mt-2"&amp;gt;
          Incorrect OTP. Please try again.
        &amp;lt;/p&amp;gt;
      )}
    &amp;lt;/div&amp;gt;
  );
};

export default Login;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This component manages the user interaction for entering their contact information, sending the OTP, and handling the login process. It includes state management for various aspects such as OTP verification, countdown timer, and error handling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backend API for OTP Generation and Sending&lt;/strong&gt;&lt;br&gt;
Next, we'll set up the backend to handle OTP generation and sending. The OTP can be sent via email or SMS based on the user's contact information.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//OTP Generation and Sending
import { sendVerificationSMS } from "@/lib/sendSMS";
import User from "@/models/user";
import { NextResponse } from "next/server";
import { connectToDB } from "@/lib/db";
import nodemailer from "nodemailer";

const generateOTP = () =&amp;gt; {
  const digits = "0123456789";
  let OTP = "";

  for (let i = 0; i &amp;lt; 4; i++) {
    OTP += digits[Math.floor(Math.random() * 10)];
  }

  return OTP;
};

const sendVerificationEmail = async (contact, otp) =&amp;gt; {
  try {
    let transporter = nodemailer.createTransport({
      service: "gmail",
      auth: {
        user: "your-email@gmail.com",
        pass: "your-email-password",
      },
    });

    let info = await transporter.sendMail({
      from: `"Your Company" &amp;lt;your-email@gmail.com&amp;gt;`,
      to: contact,
      subject: "Verification Code",
      text: `Your verification code is: ${otp}`,
    });
    return info.messageId;
  } catch (error) {
    console.error("Error sending email:", error);
    throw new Error("Error sending verification email");
  }
};

export async function POST(req, res) {
  try {
    await connectToDB();
    const otp = generateOTP();
    const { contact } = await req.json();

    const existingUser = await User.findOne({
      email: isNaN(contact) ? contact : contact + "@gmail.com",
    });

    if (isNaN(contact)) {
      await sendVerificationEmail(contact, otp);
      return NextResponse.json({
        message: "Verification code has been sent to your email",
        otp,
      });
    } else {
      await sendVerificationSMS(contact, otp);
      return NextResponse.json({
        message: "Verification code has been sent",
        otp,
      });
    }
  } catch (error) {
    console.error(error);
    return NextResponse.error(
      "An error occurred while processing the request."
    );
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This backend code handles OTP generation and sends it either via email or SMS depending on the user's input. The generateOTP function creates a random 4-digit OTP, and the sendVerificationEmail  and sendVerificationSMS functions send the OTP to the user.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;br&gt;
Implementing an OTP-based login system enhances the security of your application by adding an additional verification step. This system ensures that only users with access to the provided email or phone number can log in, protecting against unauthorized access.&lt;/p&gt;

&lt;p&gt;Feel free to modify and expand upon this basic implementation to suit your specific requirements. Happy coding!&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>react</category>
      <category>authjs</category>
      <category>otp</category>
    </item>
    <item>
      <title>Building a Secure OTP-based Login System in Next.js</title>
      <dc:creator>Abdur Rakib Rony</dc:creator>
      <pubDate>Fri, 28 Jun 2024 13:48:30 +0000</pubDate>
      <link>https://dev.to/abdur_rakibrony_97cea0e9/building-a-secure-otp-based-login-system-in-nextjs-gb0</link>
      <guid>https://dev.to/abdur_rakibrony_97cea0e9/building-a-secure-otp-based-login-system-in-nextjs-gb0</guid>
      <description>&lt;p&gt;In today's digital age, ensuring the security of user authentication is paramount. One effective method is using One-Time Passwords (OTPs) for login. In this post, we'll walk through how to implement an OTP-based login system using Next.js, with both email and phone number options.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Use OTP?&lt;/strong&gt;&lt;br&gt;
OTPs add an extra layer of security by requiring a temporary code sent to the user's email or phone number. This method reduces the risk of unauthorized access, as the code is valid for a short period.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setting Up the Frontend&lt;/strong&gt;&lt;br&gt;
We start by creating a login component that captures the user's email or phone number and handles OTP sending and verification.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//login component
"use client";
import { useState, useEffect } from "react";
import { Input } from "@/components/ui/input";
import { Lock, Mail, Phone } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
  InputOTP,
  InputOTPGroup,
  InputOTPSlot,
} from "@/components/ui/input-otp";
import { SendOTP } from "@/utils/SendOTP";
import { useRouter } from "next/navigation";
import { signIn } from "next-auth/react";

const Login = () =&amp;gt; {
  const [contact, setContact] = useState("");
  const [otp, setOtp] = useState(false);
  const [otpCode, setOtpCode] = useState("");
  const [receivedOtpCode, setReceivedOtpCode] = useState("");
  const [timeLeft, setTimeLeft] = useState(60);
  const [timerRunning, setTimerRunning] = useState(false);
  const [resendClicked, setResendClicked] = useState(false);
  const [hasPassword, setHasPassword] = useState(false);
  const [password, setPassword] = useState("");
  const [isIncorrectOTP, setIsIncorrectOTP] = useState(false);

  const router = useRouter();

  const handleSendOtp = async () =&amp;gt; {
    setOtp(true);
    startTimer();
    setResendClicked(true);
    const data = await SendOTP(contact);
    if (data?.hasPassword) {
      setHasPassword(data?.hasPassword);
    }
    if (data?.otp) {
      setReceivedOtpCode(data?.otp);
    }
  };

  const handleLogin = async () =&amp;gt; {
    if (otpCode === receivedOtpCode) {
      await signIn("credentials", {
        redirect: false,
        email: isNaN(contact) ? contact : contact + "@gmail.com",
      });
      router.push("/");
    } else {
      setIsIncorrectOTP(true);
    }
  };

  const startTimer = () =&amp;gt; {
    setTimeLeft(60);
    setTimerRunning(true);
  };

  const resendOTP = () =&amp;gt; {
    setTimerRunning(false);
    startTimer();
    setResendClicked(true);
    handleSendOtp();
  };

  useEffect(() =&amp;gt; {
    let timer;
    if (timerRunning) {
      timer = setTimeout(() =&amp;gt; {
        if (timeLeft &amp;gt; 0) {
          setTimeLeft((prevTime) =&amp;gt; prevTime - 1);
        } else {
          setTimerRunning(false);
        }
      }, 1000);
    }

    return () =&amp;gt; clearTimeout(timer);
  }, [timeLeft, timerRunning]);

  useEffect(() =&amp;gt; {
    if (contact === "" || contact === null) {
      setOtp(false);
      setOtpCode("");
      setTimeLeft(60);
      setTimerRunning(false);
      setResendClicked(false);
    }
  }, [contact]);

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;div className="relative w-full max-w-sm"&amp;gt;
        {contact === "" || isNaN(contact) ? (
          &amp;lt;Mail
            className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"
            size={20}
          /&amp;gt;
        ) : (
          &amp;lt;Phone
            className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"
            size={20}
          /&amp;gt;
        )}
        &amp;lt;Input
          type="text"
          name="contact"
          value={contact}
          placeholder="Email or phone"
          onChange={(e) =&amp;gt; setContact(e.target.value)}
          disabled={contact &amp;amp;&amp;amp; otp}
          className="pl-10"
        /&amp;gt;
      &amp;lt;/div&amp;gt;
      {hasPassword ? (
        &amp;lt;div className="relative w-full max-w-sm mt-4"&amp;gt;
          &amp;lt;Lock
            className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"
            size={20}
          /&amp;gt;
          &amp;lt;Input
            type="password"
            name="password"
            value={password}
            placeholder="Password"
            onChange={(e) =&amp;gt; setPassword(e.target.value)}
            className="pl-10"
          /&amp;gt;
        &amp;lt;/div&amp;gt;
      ) : (
        &amp;lt;div&amp;gt;
          {contact &amp;amp;&amp;amp; otp &amp;amp;&amp;amp; (
            &amp;lt;div className="text-center text-green-500 text-base mt-1"&amp;gt;
              OTP sent successfully. Please enter OTP below.
            &amp;lt;/div&amp;gt;
          )}
          {contact &amp;amp;&amp;amp; otp &amp;amp;&amp;amp; (
            &amp;lt;div className="space-y-2 w-full flex flex-col items-center justify-center my-2"&amp;gt;
              &amp;lt;InputOTP
                maxLength={4}
                value={otpCode}
                onChange={(value) =&amp;gt; setOtpCode(value)}
                isError={isIncorrectOTP}
              &amp;gt;
                &amp;lt;InputOTPGroup&amp;gt;
                  &amp;lt;InputOTPSlot index={0} /&amp;gt;
                  &amp;lt;InputOTPSlot index={1} /&amp;gt;
                  &amp;lt;InputOTPSlot index={2} /&amp;gt;
                  &amp;lt;InputOTPSlot index={3} /&amp;gt;
                &amp;lt;/InputOTPGroup&amp;gt;
              &amp;lt;/InputOTP&amp;gt;
              &amp;lt;div&amp;gt;
                {resendClicked &amp;amp;&amp;amp; timeLeft &amp;gt; 0 ? (
                  &amp;lt;p className="text-sm"&amp;gt;
                    Resend OTP available in{" "}
                    &amp;lt;span className="text-blue-500"&amp;gt;
                      {timeLeft &amp;gt; 0 ? `${timeLeft}` : ""}
                    &amp;lt;/span&amp;gt;
                  &amp;lt;/p&amp;gt;
                ) : (
                  &amp;lt;Button
                    variant="link"
                    onClick={resendOTP}
                    className="text-blue-500"
                  &amp;gt;
                    Resend OTP
                  &amp;lt;/Button&amp;gt;
                )}
              &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
          )}
        &amp;lt;/div&amp;gt;
      )}
      {receivedOtpCode ? (
        &amp;lt;Button
          onClick={handleLogin}
          className="w-full mt-4 bg-green-500 hover:bg-green-400"
        &amp;gt;
          Login
        &amp;lt;/Button&amp;gt;
      ) : (
        &amp;lt;Button
          onClick={handleSendOtp}
          className="w-full mt-4 bg-green-500 hover:bg-green-400"
        &amp;gt;
          Next
        &amp;lt;/Button&amp;gt;
      )}
      {isIncorrectOTP &amp;amp;&amp;amp; (
        &amp;lt;p className="text-red-500 text-sm text-center mt-2"&amp;gt;
          Incorrect OTP. Please try again.
        &amp;lt;/p&amp;gt;
      )}
    &amp;lt;/div&amp;gt;
  );
};

export default Login;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This component manages the user interaction for entering their contact information, sending the OTP, and handling the login process. It includes state management for various aspects such as OTP verification, countdown timer, and error handling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backend API for OTP Generation and Sending&lt;/strong&gt;&lt;br&gt;
Next, we'll set up the backend to handle OTP generation and sending. The OTP can be sent via email or SMS based on the user's contact information.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//OTP Generation and Sending
import { sendVerificationSMS } from "@/lib/sendSMS";
import User from "@/models/user";
import { NextResponse } from "next/server";
import { connectToDB } from "@/lib/db";
import nodemailer from "nodemailer";

const generateOTP = () =&amp;gt; {
  const digits = "0123456789";
  let OTP = "";

  for (let i = 0; i &amp;lt; 4; i++) {
    OTP += digits[Math.floor(Math.random() * 10)];
  }

  return OTP;
};

const sendVerificationEmail = async (contact, otp) =&amp;gt; {
  try {
    let transporter = nodemailer.createTransport({
      service: "gmail",
      auth: {
        user: "your-email@gmail.com",
        pass: "your-email-password",
      },
    });

    let info = await transporter.sendMail({
      from: `"Your Company" &amp;lt;your-email@gmail.com&amp;gt;`,
      to: contact,
      subject: "Verification Code",
      text: `Your verification code is: ${otp}`,
    });
    return info.messageId;
  } catch (error) {
    console.error("Error sending email:", error);
    throw new Error("Error sending verification email");
  }
};

export async function POST(req, res) {
  try {
    await connectToDB();
    const otp = generateOTP();
    const { contact } = await req.json();

    const existingUser = await User.findOne({
      email: isNaN(contact) ? contact : contact + "@gmail.com",
    });

    if (isNaN(contact)) {
      await sendVerificationEmail(contact, otp);
      return NextResponse.json({
        message: "Verification code has been sent to your email",
        otp,
      });
    } else {
      await sendVerificationSMS(contact, otp);
      return NextResponse.json({
        message: "Verification code has been sent",
        otp,
      });
    }
  } catch (error) {
    console.error(error);
    return NextResponse.error(
      "An error occurred while processing the request."
    );
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This backend code handles OTP generation and sends it either via email or SMS depending on the user's input. The generateOTP function creates a random 4-digit OTP, and the sendVerificationEmail  and sendVerificationSMS functions send the OTP to the user.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;br&gt;
Implementing an OTP-based login system enhances the security of your application by adding an additional verification step. This system ensures that only users with access to the provided email or phone number can log in, protecting against unauthorized access.&lt;/p&gt;

&lt;p&gt;Feel free to modify and expand upon this basic implementation to suit your specific requirements. Happy coding!&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>react</category>
      <category>authjs</category>
      <category>otp</category>
    </item>
    <item>
      <title>Page Transition In NextJS 14 App Router Using Framer Motion</title>
      <dc:creator>Abdur Rakib Rony</dc:creator>
      <pubDate>Wed, 12 Jun 2024 08:18:19 +0000</pubDate>
      <link>https://dev.to/abdur_rakibrony_97cea0e9/page-transition-in-nextjs-14-app-router-using-framer-motion-2he7</link>
      <guid>https://dev.to/abdur_rakibrony_97cea0e9/page-transition-in-nextjs-14-app-router-using-framer-motion-2he7</guid>
      <description>&lt;p&gt;&lt;strong&gt;Animating the Template with Framer Motion&lt;/strong&gt;&lt;br&gt;
To add animations, we'll create a &lt;code&gt;Template&lt;/code&gt; component. It will automatically produce beautiful page transitions after each router change or when the page loads. Create a new file called &lt;code&gt;template.tsx&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"use client";
import React from "react";
import { motion } from "framer-motion";

export default function Template({ children }: { children: React.ReactNode }) {
  return (
    &amp;lt;motion.div
      initial={{ y: 20, opacity: 0 }}
      animate={{ y: 0, opacity: 1 }}
      transition={{ ease: "easeInOut", duration: 0.5 }}
    &amp;gt;
      {children}
    &amp;lt;/motion.div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Final Thoughts&lt;/strong&gt;&lt;br&gt;
By combining Next.js with Framer Motion, we can create a professional and dynamic web layout that enhances user experience. This approach not only ensures smooth animations but also provides a structured and maintainable codebase. &lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>animation</category>
      <category>react</category>
      <category>framer</category>
    </item>
    <item>
      <title>How to Create a Xlsx or Xls Data into MySql Row Data with Node.js, Adonis.js, and Vue.js</title>
      <dc:creator>Abdur Rakib Rony</dc:creator>
      <pubDate>Tue, 11 Jun 2024 19:04:45 +0000</pubDate>
      <link>https://dev.to/abdur_rakibrony_97cea0e9/how-to-create-a-xlsx-or-xls-data-into-mysql-row-data-with-nodejs-adonisjs-and-vuejs-h7k</link>
      <guid>https://dev.to/abdur_rakibrony_97cea0e9/how-to-create-a-xlsx-or-xls-data-into-mysql-row-data-with-nodejs-adonisjs-and-vuejs-h7k</guid>
      <description>&lt;p&gt;&lt;strong&gt;Introduction&lt;/strong&gt;&lt;br&gt;
Uploading and processing Excel files is a common requirement for many applications, especially those dealing with bulk data entry. This blog post will guide you through creating a feature to upload Excel files, process their content, and display the data in a table format using Node.js, Adonis.js, and Vue.js. We will leverage the power of these frameworks to build a robust and efficient solution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prerequisites&lt;/strong&gt;&lt;br&gt;
Before we start, ensure you have the following installed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node.js&lt;/li&gt;
&lt;li&gt;Adonis.js CLI&lt;/li&gt;
&lt;li&gt;Vue.js CLI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Adonis API&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const MarketingData = use("App/Models/Admins/MarketingData");
const Database = use("Database");
const xlsx = require("xlsx");
const Helpers = use("Helpers");
class MarketingDataController {
  async uploadExcel({ request, response }) {
    try {
      const excelFile = request.file("excelfile", {
        types: ["application"],
        extnames: ["xls", "xlsx"],
        size: "2mb",
      });
      if (!excelFile) {
        return response.status(400).json({
          success: false,
          message: "Please upload an Excel file",
        });
      }
      const filePath = Helpers.tmpPath(uploads/${new Date().getTime()}_${excelFile.clientName});
      await excelFile.move(Helpers.tmpPath("uploads"), {
        name: ${new Date().getTime()}_${excelFile.clientName},
      });
      if (!excelFile.moved()) {
        return response.status(500).json({
          success: false,
          message: "Error moving the file",
          error: excelFile.error(),
        });
      }
      const workbook = xlsx.readFile(filePath);
      const sheetName = workbook.SheetNames[0];
      const sheet = workbook.Sheets[sheetName];
      const data = xlsx.utils.sheet_to_json(sheet);
      await Database.transaction(async (trx) =&amp;gt; {
        for (const record of data) {
          // Check if email or phone already exists in the database
          const existingRecord = await MarketingData.query()
            .where('email', record.email)
            .orWhere('phone', record.phone)
            .first();
          // If email or phone already exists, skip inserting this record
          if (existingRecord) {
            console.log(Skipping record with email '${record.email}' or phone '${record.phone}' as it already exists in the database.);
            continue;
          }
          // Insert the record into the database
          await MarketingData.create({
            name: record.name,
            email: record.email,
            phone: record.phone,
            remarks: record.remarks,
            created_at: new Date(),
            updated_at: new Date(),
          }, trx);
        }
      });
      return response.status(200).json({
        success: true,
        message: "Data uploaded and inserted successfully",
      });
    } catch (error) {
      console.log("File Read/Parse Error:", error);
      return response.status(500).json({
        success: false,
        message: "Server error",
      });
    }
  }
}
module.exports = MarketingDataController;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Setting Up the Frontend with Vue.js&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class="profileView pa-4 responsive-height"&amp;gt;
    &amp;lt;v-container class="custom-container"&amp;gt;
      &amp;lt;v-row&amp;gt;
        &amp;lt;v-col lg="12" md="6" sm="12" cols="12"&amp;gt;
          &amp;lt;v-card
            class="verification-card"
            flat
            tile
            :color="$vuetify.theme.dark ? '#172233' : '#F1F7FB'"
          &amp;gt;
            &amp;lt;v-card-actions class="px-0"&amp;gt;
              &amp;lt;v-card-title class="pl-1"&amp;gt;
                &amp;lt;v-icon class="pr-2" color="#708AA7"
                  &amp;gt;mdi mdi-account-outline&amp;lt;/v-icon
                &amp;gt;Import Excel Data
              &amp;lt;/v-card-title&amp;gt;
            &amp;lt;/v-card-actions&amp;gt;

            &amp;lt;v-card-subtitle&amp;gt;
              Click the box below to select file or you can drag and drop your
              &amp;lt;span&amp;gt;xls, .xlsx&amp;lt;/span&amp;gt;
              files.
            &amp;lt;/v-card-subtitle&amp;gt;
            &amp;lt;div class="zone-wrapper"&amp;gt;
              &amp;lt;v-card-text class="text-center"&amp;gt;
                &amp;lt;vue-dropzone
                  ref="myVueDropzone"
                  id="dropzone"
                  :options="dropzoneOptions"
                  @vdropzone-success="handleUpload"
                &amp;gt;&amp;lt;/vue-dropzone&amp;gt;
                &amp;lt;div class="dropzone-content"&amp;gt;
                  &amp;lt;v-icon large class="mb-3" color="#708AA7"
                    &amp;gt;mdi mdi-tray-arrow-up&amp;lt;/v-icon
                  &amp;gt;
                  &amp;lt;p class="mb-0"&amp;gt;Drop File Here or &amp;lt;strong&amp;gt;Browse&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
                  &amp;lt;span&amp;gt;File size max 2MB&amp;lt;/span&amp;gt;
                &amp;lt;/div&amp;gt;
              &amp;lt;/v-card-text&amp;gt;
              &amp;lt;div class="details"&amp;gt;
                &amp;lt;h3&amp;gt;Need Sample Excel File?&amp;lt;/h3&amp;gt;
                &amp;lt;p&amp;gt;
                  Here We Have Linked 1 Excel File for an example. To get the
                  example file, Please
                  &amp;lt;a href="/sample.xlsx" download target="_blank"&amp;gt;Click Here&amp;lt;/a&amp;gt;
                &amp;lt;/p&amp;gt;
              &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/v-card&amp;gt;
        &amp;lt;/v-col&amp;gt;
      &amp;lt;/v-row&amp;gt;
      &amp;lt;v-row&amp;gt;
        &amp;lt;v-col cols="12"&amp;gt;
          &amp;lt;v-card
            elevation="0"
            :color="$vuetify.theme.dark ? '#131c29' : '#E0EAF2'"
          &amp;gt;
            &amp;lt;v-row class="mb-1"&amp;gt;
              &amp;lt;v-col cols="12"&amp;gt;
                &amp;lt;v-text-field
                  v-model="search"
                  prepend-icon="mdi-magnify"
                  label="Search here..."
                  single-line
                  solo
                  hide-details
                  class="custom-v-input"
                &amp;gt;&amp;lt;/v-text-field&amp;gt;
              &amp;lt;/v-col&amp;gt;
            &amp;lt;/v-row&amp;gt;
            &amp;lt;v-row dense&amp;gt;
              &amp;lt;v-col cols="12" sm="12"&amp;gt;
                &amp;lt;v-card
                  class="pa-3 rounded-0 elevation-0"
                  :color="$vuetify.theme.dark ? '#172233' : '#E8F1F8'"
                &amp;gt;
                  &amp;lt;v-sheet
                    class="transactionTable depositTabTable"
                    :color="$vuetify.theme.dark ? '#1A2A3E ' : '#F1F7FB'"
                  &amp;gt;
                    &amp;lt;v-data-table
                      :headers="headers"
                      :items="marketingusers"
                      :search="search"
                      :items-per-page="10"
                    &amp;gt;
                      &amp;lt;template v-slot:item.sl="{ item, index }"&amp;gt;
                        {{ index + 1 }}
                      &amp;lt;/template&amp;gt;
                    &amp;lt;/v-data-table&amp;gt;
                  &amp;lt;/v-sheet&amp;gt;
                &amp;lt;/v-card&amp;gt;
              &amp;lt;/v-col&amp;gt;
            &amp;lt;/v-row&amp;gt;
          &amp;lt;/v-card&amp;gt;
        &amp;lt;/v-col&amp;gt;
      &amp;lt;/v-row&amp;gt;
    &amp;lt;/v-container&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
import vue2Dropzone from "vue2-dropzone";
import "vue2-dropzone/dist/vue2Dropzone.min.css";
import { mapState, mapActions } from "vuex";
import { MARKETINGDATA__ACTIONS } from "@/store/action-types";

export default {
  name: "dropzoneView",
  data() {
    return {
      search: "",
      dropzoneOptions: {
        url: "#", // Not used as we handle the upload manually
        thumbnailWidth: 100,
        maxFilesize: 2, // 2MB limit
        acceptedFiles: ".xlsx,.xls", // Only accept these file types
        headers: { "My-Awesome-Header": "header value" },
      },
      headers: [
        { text: "SL", value: "sl", align: "start" },
        { text: "Name", value: "name" },
        { text: "Email", value: "email" },
        { text: "Mobile No", value: "phone" },
        { text: "Remarks", value: "remarks" },
      ],
    };
  },
  components: {
    vueDropzone: vue2Dropzone,
  },
  computed: {
    ...mapState("marketingdata", ["marketingusers"]),
  },
  methods: {
    ...mapActions("marketingdata", [MARKETINGDATA__ACTIONS.GETMARKETINGUSERS]),
    async handleUpload(file) {
      const formData = new FormData();
      formData.append("file", file);

      // Dispatch Vuex action to upload file
      await this.$store.dispatch("marketingdata/uploadFile", formData);
    },
  },
  async mounted() {
    await this[MARKETINGDATA__ACTIONS.GETMARKETINGUSERS]();
  },
};
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>node</category>
      <category>vue</category>
      <category>adonis</category>
      <category>vuex</category>
    </item>
    <item>
      <title>Elevate Your Next.js E-commerce App with Google Tag Manager</title>
      <dc:creator>Abdur Rakib Rony</dc:creator>
      <pubDate>Fri, 07 Jun 2024 02:12:18 +0000</pubDate>
      <link>https://dev.to/abdur_rakibrony_97cea0e9/elevate-your-nextjs-e-commerce-app-with-google-tag-manager-1ke0</link>
      <guid>https://dev.to/abdur_rakibrony_97cea0e9/elevate-your-nextjs-e-commerce-app-with-google-tag-manager-1ke0</guid>
      <description>&lt;p&gt;Google Tag Manager simplifies the process of adding and updating tags (snippets of code) on your website without modifying the source code. It's a game-changer for marketers and analysts who need agility in tracking various events.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integrating GTM with Next.js:
&lt;/h2&gt;

&lt;p&gt;Next.js, a React framework, has gained popularity for its simplicity and performance optimizations. Let's see how we can seamlessly integrate GTM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { GoogleTagManager } from '@next/third-parties/google'
import { Suspense } from "react";

// In your layout component
&amp;lt;Suspense fallback={null}&amp;gt;
  &amp;lt;GoogleTagManager gtmId="GTM-******" /&amp;gt;
&amp;lt;/Suspense&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we're using @next/third-parties/google, an official package that simplifies third-party script integration in Next.js. The GoogleTagManager component takes your GTM container ID (gtmId). We wrap it in a Suspense component with a null fallback to avoid any layout shifts during loading.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tracking Product Views:
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"use client";
import { useEffect } from "react";

// In your product detail component
useEffect(() =&amp;gt; {
  if (product) {
    window.dataLayer = window.dataLayer || [];
    window.dataLayer.push({ ecommerce: null });
    window.dataLayer.push({
      event: "view_item",
      ecommerce: {
        currency: "BDT",
        value: product?.price,
        items: [
          {
            item_id: product?._id,
            item_name: product?.title,
            item_category: product?.category,
            item_variant: product?.category || "",
            price: product?.price,
            quantity: product?.quantity,
          },
        ],
      },
    });
  }
}, [product]);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Breaking down the code:
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;"use client:"&lt;/strong&gt; This is a Next.js directive indicating that the following code should run on the client-side.&lt;br&gt;
&lt;strong&gt;useEffect:&lt;/strong&gt; A React hook that runs after the component renders. Here, it runs when the product changes.&lt;br&gt;
&lt;strong&gt;window.dataLayer:&lt;/strong&gt; This is how GTM receives data. We initialize it if it doesn't exist.&lt;br&gt;
&lt;strong&gt;dataLayer.push({ ecommerce: null }):&lt;/strong&gt; This clears any previous ecommerce data to avoid conflicts.&lt;br&gt;
&lt;strong&gt;dataLayer.push({ ... }):&lt;/strong&gt; We push the view_item event data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;event:&lt;/strong&gt; "view_item" is a standard GA4 ecommerce event.&lt;br&gt;
&lt;strong&gt;ecommerce.currency:&lt;/strong&gt; The currency code (BDT for Bangladeshi Taka).&lt;br&gt;
&lt;strong&gt;ecommerce.value:&lt;/strong&gt; The discounted price of the product.&lt;br&gt;
&lt;strong&gt;ecommerce.items:&lt;/strong&gt; An array of items viewed. In this case, just one product. item_id, item_name, item_category, item_variant, price, and quantity are standard GA4 product properties.&lt;/p&gt;

</description>
      <category>react</category>
      <category>nextjs</category>
      <category>gtm</category>
    </item>
  </channel>
</rss>
