Next.js application using TypeScript and Prisma following the MVC pattern

Jun 04, 202445 mins read

pexels-pixabay-38568-1.jpg

 Define the Prisma schema for your Attribute and AttributeValue models.
Controller: Create a controller function to fetch and return the attributes.
View: Implement a Next.js page component to display the attributes.
Below is a step-by-step conversion:

1. Prisma Schema Definition
First, define your Prisma models in the schema.prisma file:

// schema.prisma
model Attribute {
  id              Int               @id @default(autoincrement())
  name            String
  createdAt       DateTime          @default(now())
  attributeValues AttributeValue[]
  translations    AttributeTranslation[]
}

model AttributeValue {
  id          Int        @id @default(autoincrement())
  value       String
  attributeId Int
  attribute   Attribute  @relation(fields: [attributeId], references: [id])
}

model AttributeTranslation {
  id          Int       @id @default(autoincrement())
  lang        String
  name        String
  attributeId Int
  attribute   Attribute  @relation(fields: [attributeId], references: [id])
}

2. Prisma Client Initialization
Initialize Prisma Client in your project:

npx prisma generate

3. Controller Function
Create a controller function to fetch the attributes along with their values. You can place this in a file like controllers/attributeController.ts.

// controllers/attributeController.ts
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

export async function getAttributes(page: number = 1, pageSize: number = 15) {
  const skip = (page - 1) * pageSize;

  const attributes = await prisma.attribute.findMany({
    include: { attributeValues: true },
    orderBy: { createdAt: 'desc' },
    skip: skip,
    take: pageSize,
  });

  const totalAttributes = await prisma.attribute.count();

  return {
    attributes,
    totalAttributes,
    totalPages: Math.ceil(totalAttributes / pageSize),
    currentPage: page,
  };
}

4. API Route
Create an API route to handle the request and call the controller function. Place this in pages/api/attributes.ts.

// pages/api/attributes.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { getAttributes } from '../../controllers/attributeController';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  const page = parseInt(req.query.page as string) || 1;
  const pageSize = parseInt(req.query.pageSize as string) || 15;

  try {
    const data = await getAttributes(page, pageSize);
    res.status(200).json(data);
  } catch (error) {
    console.error(error);
    res.status(500).json({ error: 'Internal Server Error' });
  }
}

5. View (Next.js Page)
Create a Next.js page to display the attributes. Place this in pages/attributes.tsx.

// pages/attributes.tsx
import { useEffect, useState } from 'react';
import axios from 'axios';

interface Attribute {
  id: number;
  name: string;
  createdAt: string;
  attributeValues: { id: number; value: string }[];
}

interface AttributesPageProps {
  initialAttributes: Attribute[];
  totalPages: number;
  currentPage: number;
}

const AttributesPage = ({ initialAttributes, totalPages, currentPage }: AttributesPageProps) => {
  const [attributes, setAttributes] = useState(initialAttributes);
  const [page, setPage] = useState(currentPage);

  useEffect(() => {
    if (page !== currentPage) {
      axios.get(`/api/attributes?page=${page}`)
        .then(response => {
          setAttributes(response.data.attributes);
        })
        .catch(error => {
          console.error('Error fetching attributes:', error);
        });
    }
  }, [page]);

  return (
    <div>
      <h1>Attributes</h1>
      <ul>
        {attributes.map(attribute => (
          <li key={attribute.id}>
            <h2>{attribute.name}</h2>
            <ul>
              {attribute.attributeValues.map(value => (
                <li key={value.id}>{value.value}</li>
              ))}
            </ul>
          </li>
        ))}
      </ul>
      <div>
        {Array.from({ length: totalPages }, (_, index) => (
          <button key={index} onClick={() => setPage(index + 1)} disabled + 1 === page}>
            {index + 1}
          </button>
        ))}
      </div>
    </div>
  );
};

export async function getServerSideProps() {
  const response = await axios.get('http://localhost:3000/api/attributes');
  const { attributes, totalPages, currentPage } = response.data;

  return {
    props: {
      initialAttributes: attributes,
      totalPages,
      currentPage,
    },
  };
}

export default AttributesPage;

Summary
Model: Defined using Prisma in schema.prisma.
Controller: Implemented in controllers/attributeController.ts.
API Route: Created in pages/api/attributes.ts.
View: Built using a Next.js page in pages/attributes.tsx.
This setup follows the MVC pattern, with Prisma managing the data model, a controller function handling business logic, an API route serving as the intermediary, and a Next.js page rendering the view.

Create Controller
Create a controller function to handle the creation of attributes. Place this in a file like controllers/attributeController.ts.

// controllers/attributeController.ts
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

export async function getAttributes() {
  return prisma.attribute.findMany({
    include: { attributeValues: true, translations: true },
    orderBy: { createdAt: 'desc' },
  });
}

export async function createAttribute(name: string, lang: string = 'en') {
  return prisma.attribute.create({
    data: {
      name,
      translations: {
        create: {
          lang,
          name,
        },
      },
    },
  });
}

Set up API Route
Create an API route to handle the POST request for creating a new attribute. Place this in pages/api/attributes.ts.

// pages/api/attributes.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { getAttributes, createAttribute } from '@/app/server/controllers/AttributeController';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  if (req.method === 'GET') {
    try {
      const attributes = await getAttributes();
      res.status(200).json({ attributes });
    } catch (error) {
      console.error(error);
      res.status(500).json({ error: 'Internal Server Error' });
    }
  } else if (req.method === 'POST') {
    const { name } = req.body;

    try {
      const attribute = await createAttribute(name);
      res.status(201).json({ attribute });
    } catch (error) {
      console.error(error);
      res.status(500).json({ error: 'Internal Server Error' });
    }
  } else {
    res.setHeader('Allow', ['GET', 'POST']);
    res.status(405).end(`Method ${req.method} Not Allowed`);
  }
}

Create View (Next.js Page)
Create a Next.js page with a form to submit new attributes. Place this in pages/attributes/new.tsx.

// pages/attributes/new.tsx
import { useState } from 'react';
import axios from 'axios';
import { useRouter } from 'next/router';

const NewAttributePage = () => {
  const [name, setName] = useState('');
  const router = useRouter();

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    try {
      await axios.post('/api/attributes', { name });
      alert('Attribute has been inserted successfully');
      router.push('/attributes');
    } catch (error) {
      console.error('Error creating attribute:', error);
      alert('Failed to create attribute');
    }
  };

  return (
    <div>
      <h1>Create New Attribute</h1>
      <form onSubmit={handleSubmit}>
        <div>
          <label htmlFor="name">Name</label>
          <input
            type=text
            id="name"
            value={name}
            onChange={(e) => setName(e.target.value)}
            required
          />
        </div>
        <button type=submit>Create Attribute</button>
      </form>
    </div>
  );
};

export default NewAttributePage;

Summary
Model: Defined using Prisma in schema.prisma.
Controller: Implemented in controllers/attributeController.ts.
API Route: Created in pages/api/attributes.ts to handle POST requests.
View: Built a form using a Next.js page in pages/attributes/new.tsx.
This setup follows the MVC pattern, with Prisma managing the data model, a controller function handling the business logic of creating an attribute, an API route serving as the intermediary, and a Next.js page rendering the form to create new attributes. This approach ensures a clean separation of concerns and a scalable structure for your application.

Next.js application using TypeScript and Prisma, you need to handle updating an attribute and its translations

Define Prisma Schema
Ensure your Prisma schema includes models for Attribute and AttributeTranslation.

2. Create Controller Function
Implement a controller function to handle attribute updates. Place this function in a file like controllers/attributeController.ts.

// controllers/attributeController.ts
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

export async function updateAttribute(id: number, name: string, lang: string = 'en') {
  const attribute = await prisma.attribute.update({
    where: { id },
    data: {
      name,
      translations: {
        upsert: {
          where: { lang_attributeId: { lang, attributeId: id } },
          update: { name },
          create: { lang, name, attribute: { connect: { id } } },
        },
      },
    },
    include: { translations: true },
  });

  return attribute;
}

3. Set up API Route
Create an API route to handle the PUT request for updating an attribute. Place this in pages/api/attributes/[id].ts.

// pages/api/attributes/[id].ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { updateAttribute } from '@/app/server/controllers/attributeController';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  const { id } = req.query;

  if (req.method === 'PUT') {
    const { name, lang } = req.body;

    try {
      const attribute = await updateAttribute(Number(id), name, lang);
      res.status(200).json({ attribute });
    } catch (error) {
      console.error(error);
      res.status(500).json({ error: 'Internal Server Error' });
    }
  } else {
    res.setHeader('Allow', ['PUT']);
    res.status(405).end(`Method ${req.method} Not Allowed`);
  }
}

4. Create View (Next.js Page)
Implement a form in a Next.js page to allow users to update attributes. Place this in pages/attributes/[id]/edit.tsx.

// pages/attributes/[id]/edit.tsx
import { useState, useEffect } from 'react';
import axios from 'axios';
import { useRouter } from 'next/router';

const EditAttributePage = () => {
  const router = useRouter();
  const { id } = router.query;
  const [name, setName] = useState('');
  const [lang, setLang] = useState('en');

  useEffect(() => {
    if (id) {
      axios.get(`/api/attributes/${id}`)
        .then(response => {
          const { name } = response.data.attribute;
          setName(name);
        })
        .catch(error => {
          console.error('Error fetching attribute:', error);
        });
    }
  }, [id]);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    try {
      await axios.put(`/api/attributes/${id}`, { name, lang });
      alert('Attribute has been updated successfully');
      router.push('/attributes');
    } catch (error) {
      console.error('Error updating attribute:', error);
      alert('Failed to update attribute');
    }
  };

  return (
    <div>
      <h1>Edit Attribute</h1>
      <form onSubmit={handleSubmit}>
        <div>
          <label htmlFor="name">Name</label>
          <input
            type=text
            id="name"
            value={name}
            onChange={(e) => setName(e.target.value)}
            required
          />
        </div>
        <div>
          <label htmlFor="lang">Language</label>
          <select id="lang" value={lang} onChange={(e) => setLang(e.target.value)}>
            <option value="en">English</option>
            {/* Add options for other languages */}
          </select>
        </div>
        <button type=submit>Update Attribute</button>
      </form>
    </div>
  );
};

export default EditAttributePage;

Summary
Controller: The updateAttribute function in controllers/attributeController.ts handles updating an attribute and its translation.
API Route: The API route in pages/api/attributes/[id].ts handles the PUT request for updating an attribute.
View: The form in pages/attributes/[id]/edit.tsx allows users to update attributes.
This setup ensures that your Next.js application can handle attribute updates while maintaining a clean MVC structure. Adjust the code as needed to fit your specific requirements.

Share
Newsletter

Subscribe our newsletter