Search code examples
typescriptnext.jsbackendapi-designprisma

TypeError when using cursor in Prisma


I'm using Prisma (4.2.1) in a Next.js API Route for cursor-based pagination of posts.

When I pass the cursor to the API endpoint, I get the following error message (500) in the console:

TypeError: Cannot read properties of undefined (reading 'createdAt')
    at getPost (webpack-internal:///(api)/./lib/api/post.ts:67:46)
error - TypeError [ERR_INVALID_ARG_TYPE]: The "string" argument must be of type string or an instance of Buffer or ArrayBuffer. Received an instance of TypeError

I'm using Postman to access the API endpoint.

When I remove the cursor from the API Route, there are no errors and the posts are returned as expected.

I've tried upgrading to the latest Prisma version (4.2.1), using .toString() on the cursor, and changing the AllPosts interface to 'any' but I've been unable to solve the TypeError.

How can I fix this error and get Prisma to accept the cursor as valid?

API Route

import prisma from "@/lib/prisma";
import type { NextApiRequest, NextApiResponse } from "next";
import type { Post, Site } from ".prisma/client";
import type { Session } from "next-auth";
import { revalidate } from "@/lib/revalidate";
import type { WithSitePost } from "@/types";

interface AllPosts {
  posts: Array<Post>;
  site: Site | null;
}

export async function getPost(
  req: NextApiRequest,
  res: NextApiResponse,
  session: Session
): Promise<void | NextApiResponse<AllPosts | (WithSitePost | null)>> {
  const { postId, siteId, published, cursor } = req.query;

  if (
    Array.isArray(postId) ||
    Array.isArray(siteId) ||
    Array.isArray(published) ||
    Array.isArray(cursor)
  )
    return res.status(400).end("Bad request. Query parameters are not valid.");

  if (!session.user.id)
    return res.status(500).end("Server failed to get session user ID");

  try {
    if (postId) {
      const post = await prisma.post.findFirst({
        where: {
          id: postId,
          site: {
            user: {
              id: session.user.id,
            },
          },
        },
        include: {
          site: true,
        },
      });

      return res.status(200).json(post);
    }

    const site = await prisma.site.findFirst({
      where: {
        id: siteId,
        user: {
          id: session.user.id,
        },
      },
    });

    const posts = !site
      ? []
      : await prisma.post.findMany({
          take: 10,
          skip: cursor === undefined ? 0 : 1,
          cursor: {
            id: cursor,
          },
          where: {
            site: {
              id: siteId,
            },
            published: JSON.parse(published || "true"),
          },
          orderBy: {
            createdAt: "desc",
          },
        });

    const lastPostInResults = posts[9];
    const nextCursor = lastPostInResults.createdAt;

    return res.status(200).json({
      posts,
      site,
      nextCursor,
    });
  } catch (error) {
    console.error(error);
    return res.status(500).end(error);
  }
}

Prisma Schema

model Post {
  id            String   @id @default(cuid())
  title         String?  @db.Text
  content       String?  @db.LongText
  slug          String   @default(cuid())
  createdAt     DateTime @default(now())
  updatedAt     DateTime @updatedAt
  published     Boolean  @default(false)
  site          Site?    @relation(fields: [siteId], references: [id], onDelete: Cascade)
  siteId        String?

  @@unique([id, siteId], name: "post_site_constraint")
}

model Site {
  id            String        @id @default(cuid())
  name          String?
  createdAt     DateTime      @default(now())
  updatedAt     DateTime      @updatedAt
  user          User?         @relation(fields: [userId], references: [id])
  userId        String?
  posts         Post[]
}

Solution

  • This was solved by adding @unique to the Post model, pushing the schema changes and using prisma generate.

    model Post {
      createdAt     DateTime @default(now())
      ...
    }