DEV Community

AjeaS
AjeaS

Posted on

Coding Snippets Dev Log (10/14/23)

Update (10/14/23):

topics covered:

  • using Prisma for ORM with mongoDB
  • fetching data sidebar component
  • dynamic routes (layout wrapper)

Prisma for ORM with mongoDB

I followed this guide to get started with using Prisma with mongoDB.
In a nutshell;

  • it generates a prisma/schema.prisma file where I store my models.
  • initialized a .env for my DB connection string.

this is what my schema looks like so far. I wanted to keep it simple.

prisma/schema.prisma


// This is your Prisma schema file,
// Learn more about it in the docs: https://pris.ly/d/prisma-schema

generator client {
  provider = "prisma-client-js"
}

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

model CodeSnippet {
  id     String      @id @default(auto()) @map("_id") @db.ObjectId
  title  String
  code   String

  folder Folder?  @relation(fields: [folderId], references: [id])
  folderId String? @db.ObjectId
}

model Folder {
  id       String        @id @default(auto()) @map("_id") @db.ObjectId
  name     String
  icon     String
  snippets CodeSnippet[]
}
Enter fullscreen mode Exit fullscreen mode

Each Folder can have multiple CodeSnippets. As well a CodeSnippet can belong to a Folder.

Lastly, I installed prisma client to query the DB on the front-end with npm install @prisma/client

I created an instance of prisma client to reuse throughout
src/lib/db.ts

import { PrismaClient } from '@prisma/client';

declare global {
  var prisma: PrismaClient | undefined;
}

const prisma = global.prisma || new PrismaClient();

if (process.env.NODE_ENV === 'development') global.prisma = prisma;

export default prisma;
Enter fullscreen mode Exit fullscreen mode

With some googling, I found that the reason for doing it this way. In development, because of hot-reload, a new instance of the Prisma client gets created each time, which results in multiple instances (not good). But, using a global var keeps the same instance on each refresh. Therefore, we need to set it globally in development.

fetching data sidebar component

In the SideBar.tsx file I'm using CSS modules for styling and I split it up into 3 sections.

  • user section where the user image will be after login
  • the list of folders fetched from the DB
  • Add a folder button component, that when clicked shows a prompt
import Image from 'next/image';
import AddFolderButton from '../AddFolderButton';
import FolderList from '../FolderList';
import styles from './SideBar.module.css';

export default async function SideBarMenu() {
  return (
    <aside className={styles.aside}>
      {/* USER */}
      <section className={styles.user_section}>
        <Image
          priority
          src="/user.png"
          width={100}
          height={100}
          alt="Picture of author"
        />
        <h3>John Doe</h3>
      </section>

      {/* FOLDERS */}
      <section className={styles.folder_list}>
        <h5>Folders</h5>
        <FolderList />
      </section>

      {/* ADD FOLDER */}
      <section className={styles.new_folder_btn}>
        <AddFolderButton />
      </section>
    </aside>
  );
}
Enter fullscreen mode Exit fullscreen mode

The add folder button is a client component, so I added the 'use client' directive at the top.

'use client';
import { createFolder } from '@/lib/folders/actions';
import Image from 'next/image';

const AddFolderButton = () => {
  async function handleSubmit() {
    let folderName: string | null = prompt('Please enter folder name:');

    if (folderName !== null) {
      await createFolder(folderName);
    }
    return;
  }

  return (
    <button onClick={handleSubmit}>
      <Image src="/plus.png" alt="plus" width={24} height={24} />
      <span>New Folder</span>
    </button>
  );
};

export default AddFolderButton;

Enter fullscreen mode Exit fullscreen mode

The createFolder func takes in the folder name from the prompt and creates that folder. After, I revalidate the path which updates the cache and fetches new data.

export async function createFolder(name: string) {
  try {
    await prisma.folder.create({
      data: {
        name,
        icon: 'folder',
      },
    });

    revalidatePath('/');
  } finally {
    await prisma.$disconnect();
  }
}
Enter fullscreen mode Exit fullscreen mode

The getFolders func fetches all folders from DB. I currently haven't implemented the error handling yet.

export const getFolders = cache(async () => {
  try {
    console.log('Its called');
    const folders = await prisma.folder.findMany();

    if (!folders.length) {
      // Handle error - look at docs
    }

    return folders;
  } finally {
    await prisma.$disconnect();
  }
});
Enter fullscreen mode Exit fullscreen mode

Lastly, I fetched the data from FolderList.tsx component

import { getFolders } from '@/lib/folders/actions';
import Folder from './Folder';

const FolderList = async () => {
  const folders = await getFolders();

  return (
    <ul>
      {folders.map((folder) => {
        return <Folder folder={folder} key={folder.id} />;
      })}
    </ul>
  );
};

export default FolderList;

Enter fullscreen mode Exit fullscreen mode

Folder.tsx

'use client'
import { Folder } from '@/lib/types';
import Link from 'next/link';
import { usePathname } from 'next/navigation'
import styles from '../components/Sidebar/SideBar.module.css';

const Folder = ({ folder }: { folder: Folder }) => {
  const pathname = usePathname();
  const decodedPathname = decodeURIComponent(pathname).replace(/%20/g, ' ');
  return (
    <Link href={`/folder/${folder.name}`} key={folder.id}>
      <div
        className={
          decodedPathname == `/folder/${folder.name}` ? `${styles.active}` : ''
        }
      >
        {folder.name === 'Favorites' ? (
          <svg
            width="24"
            height="24"
            viewBox="0 0 24 24"
            fill="none"
            xmlns="http://www.w3.org/2000/svg"
          >
            <path
              d="M21.9843 10.7231L17.7562 14.4131L19.0228 19.9069C19.0898 20.1941 19.0707 20.4947 18.9678 20.771C18.8649 21.0474 18.6828 21.2874 18.4443 21.4608C18.2058 21.6343 17.9215 21.7336 17.6268 21.7464C17.3322 21.7591 17.0404 21.6847 16.7878 21.5325L11.9962 18.6263L7.21495 21.5325C6.96236 21.6847 6.6705 21.7591 6.37586 21.7464C6.08122 21.7336 5.79688 21.6343 5.55838 21.4608C5.31988 21.2874 5.13781 21.0474 5.03493 20.771C4.93205 20.4947 4.91292 20.1941 4.97995 19.9069L6.24464 14.4188L2.01557 10.7231C1.79189 10.5302 1.63015 10.2756 1.55063 9.99108C1.4711 9.70661 1.47733 9.40499 1.56855 9.12404C1.65976 8.8431 1.83189 8.59534 2.06335 8.41183C2.29481 8.22832 2.57529 8.11724 2.86964 8.0925L8.44401 7.60969L10.6199 2.41969C10.7336 2.14736 10.9252 1.91474 11.1708 1.75112C11.4164 1.5875 11.7049 1.50019 11.9999 1.50019C12.295 1.50019 12.5835 1.5875 12.8291 1.75112C13.0747 1.91474 13.2663 2.14736 13.3799 2.41969L15.5624 7.60969L21.1349 8.0925C21.4293 8.11724 21.7098 8.22832 21.9412 8.41183C22.1727 8.59534 22.3448 8.8431 22.436 9.12404C22.5272 9.40499 22.5335 9.70661 22.454 9.99108C22.3744 10.2756 22.2127 10.5302 21.989 10.7231H21.9843Z"
              fill="#FCCA46"
            />
          </svg>
        ) : (
          <svg
            width="25"
            height="24"
            viewBox="0 0 25 24"
            fill="none"
            xmlns="http://www.w3.org/2000/svg"
          >
            <path
              d="M20.4231 6.75001H12.4156L9.83339 4.18969C9.69342 4.0498 9.5268 3.93889 9.34322 3.86341C9.15964 3.78792 8.96275 3.74938 8.76401 3.75001H3.78211C3.38088 3.75001 2.99609 3.90804 2.71238 4.18935C2.42867 4.47065 2.26929 4.85218 2.26929 5.25001V18.8081C2.26979 19.1904 2.42316 19.5568 2.69576 19.8271C2.96837 20.0974 3.33796 20.2495 3.72349 20.25H20.5073C20.886 20.2495 21.2491 20.1001 21.517 19.8346C21.7848 19.569 21.9355 19.209 21.936 18.8334V8.25001C21.936 7.85218 21.7766 7.47065 21.4929 7.18935C21.2092 6.90804 20.8244 6.75001 20.4231 6.75001ZM3.78211 5.25001H8.76401L10.2768 6.75001H3.78211V5.25001ZM20.4231 18.75H3.78211V8.25001H20.4231V18.75Z"
              fill={
                decodedPathname == `/folder/${folder.name}`
                  ? '#F4FAFF'
                  : '#FE6C0B'
              }
            />
          </svg>
        )}
        <li>{folder.name}</li>
      </div>
    </Link>
  );
};

export default Folder;
Enter fullscreen mode Exit fullscreen mode

Dynamic Routes

In src/app/folder/[name]/page.tsx

When one of the sidebar menu links is clicked ->
<Link href={/folder/${folder.name}} key={folder.id}>
it redirects to this dynamic route. Right now, I have it rendering the params.

import Layout from '@/app/page';

const FolderPage = ({ params }: { params: { name: string } }) => {
  const decodedParams = decodeURIComponent(params.name).replace(/%20/g, ' ');
  return <Layout>{decodedParams}</Layout>;
};

export default FolderPage;

Enter fullscreen mode Exit fullscreen mode

Later, I will have it render out all the code snippets within that folder.

Top comments (0)