Gary
Gary

Reputation: 1017

How do I resolve the Typescript error that's being reported by my Next.js route?

import type { NextApiRequest, NextApiResponse } from "next";

import db from "../../app/libs/dbConn";

interface DataProps {
  auth: [
    {
      name?: string;
      email?: string;
      passwordHash?: string;
    }
  ];

  status: number;
  message: string;
}

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse<DataProps>
) {
  if (req.method === "POST") {
    const data = {
      name: "Gary",
      email: `gary@${Math.floor(Math.random() * 9999999)}.subdomain.com`,
      passwordHash: "-",
    };

    const user = await db.user.create({ data });

    return res
      .status(200)
      .json({ auth: [data], status: 201, message: "created user" });
  }

  if (req.method === "GET") {
    const allUsers = await db.user.findMany();

    const users = allUsers.map((user) => {
      return {
        name: user.name || "",
        email: user.email || "",
        passwordHash: "" || "",
      };
    });

    return res
      .status(200)
      .json({ auth: users, status: 200, message: "success" });
  }

  return res.status(404).json({
    auth: [{}],
    status: 405,
    message: "http verb not supported",
  });
}

In the above "GET" section, Typescript reports "auth" as having an error. The error is -- Type '{ name: string; email: string; passwordHash: string; }[]' is not assignable to type '[{ name?: string | undefined; email?: string | undefined; passwordHash?: string | undefined; }]'. Target requires 1 element(s) but source may have fewer.ts(2322) user.ts(6, 3): The expected type comes from property 'auth' which is declared here on type 'DataProps'

I don't understand what I'm doing wrong here. Everything appears to be fine and if I ignore the Typescript error, it works as designed.

I'm missing something that I can't resolve.

Upvotes: 0

Views: 673

Answers (1)

Josh Kelley
Josh Kelley

Reputation: 58342

You've declared auth as [ { name?: string; email?: string; passwordHash?: string; }] - in other words, it's a single-element tuple containing exactly one { name?: string; email?: string; passwordHash?: string; } element. But the code is instead expecting { name?: string; email?: string; passwordHash?: string; }[] - in other words, an array of any number of { name?: string; email?: string; passwordHash?: string; } elements. So change DataProps.auth to { name?: string; email?: string; passwordHash?: string; }[], and your code should work.

For more information, see the TypeScript Handbook's discussion of arrays and tuples.

More broadly, there are a few approaches you could take to declaring your types:

  • A 1-tuple ([{ name?: string; email?: string; passwordHash?: string; }]) means that it always contains a single element. That's an odd choice (if I only had exactly one item, I'd just return it directly as { name?: string; email?: string; passwordHash?: string }), but maybe your design has specific needs for it.
  • An array ({ name?: string; email?: string; passwordHash?: string }[]) can have 0, 1, or unlimited items.
  • If you want to require all of the properties on a real User object but return an empty object to indicate an error, you'd use a union type (({ name: string; email: string; passwordHash: string } | Record<string, never>)[]). (For an explanation of Record<string, never>, see here.)
  • Personally, instead of returning an empty object, I'd make the entire auth property optional:
    interface User {
      name: string;
      email: string;
      passwordHash: string;
    }
    
    interface DataProps {
      auth?: User[];
    
      status: number;
      message: string;
    }
    
    Or, probably better, use a union for the whole thing, to declare different types for success and error responses. Then TypeScript won't let me access properties without first checking that the response was successful. Taking advantage of TypeScript's type system like this can help make code easier to maintain.
    interface DataProps {
      auth: User[];
    }
    interface ErrorResponse {
      status: number;
      message: string;
      error: true;
      errorDetails?: ErrorDetails; // for example
    }
    type SuccessResponse<T> = T & {
      status: number;
      message: string;
      error?: false;
    }
    export default async function handler(
      req: NextApiRequest,
      res: NextApiResponse<DataProps>
    ) {
    

Upvotes: 1

Related Questions