TanStackT
TanStack13mo ago
5 replies
popular-magenta

Returning custom errors from createServerFn

In Next.js, I can't control anything about the response of a server action. It's either 200 OK with response data, or it's 500 where next assumes an unrecoverable error happened, when in reality I just want to return for example a 404 with the message "Could not find a user with that phone number". This means I have to resort to an ungly hack where I wrap my own ok: true or ok: false just to be able to pass any errors back to the client. This is what my nextjs solution is
// type LoginActionResponse =
//     | { ok: true; data: Pick<LoginResponse, "username" | "image" | "verified"> }
//     | {
//             ok: false;
//             code:
//                 | "InvalidCredential"
//                 | "InvalidLogin"
//                 | "Unknown"
//                 | "MissingTOTP"
//                 | "WrongTOTP";
//       };

// Create a custom refinement for validating a phone number
const phoneNumberSchema = z
    .string()
    .refine((value) => true, { message: "Invalid phone number" });

// Create a schema for validating an email
const emailSchema = z.string().email({ message: "Invalid email address" });

// Combine the phone and email schemas into one using z.union
const credentialSchema = z.union([phoneNumberSchema, emailSchema]);

// Create the full login schema
const loginSchema = z.object({
    credential: credentialSchema,
    password: z.string(),
    isrememberme: z.boolean(),
});

export const login = createServerFn({ method: "POST" })
    .validator(loginSchema)
    .handler(async ({ data }) => {
        const res = await fetch(
            `${baseUrl}login`,
            postJson({
                credential: data.credential,
                password: data.password,
            }),
        );

        try {
            const userData = (await handleJsonWithCodeIfOk(
                res,
                "login",
            )) as LoginResponse;
            await setAuthCookies(userData);
            return {
                ok: true,
                data: {
                    username: userData.username,
                    image: userData.image,
                    verified: userData.verified,
                },
            };
        } catch (e) {
            if (e instanceof Error) {
                return {
                    ok: false,
                    code: e.message as any,
                };
            }
            return {
                ok: false,
                code: "Unknown",
            };
        }
    });

And this is what I ideally want
export const login = createServerFn({ method: "POST" })
    .validator(loginSchema)
    .handler(async ({ data }) => {
        const res = await fetch(
            `${baseUrl}login`,
            postJson({
                credential: data.credential,
                password: data.password,
            }),
        );

        if (!res.ok) {
            const err = await res.json();
                        // Not sure which vinxi method is best to use
            sendError(err);
        }

        const userData = (await res.json()) as LoginResponse;
                // My own function that just wraps setCookie from vinxi/http
        setAuthCookies(userData);
        return userData;
    });

How are you all returning errors from your server function?
Was this page helpful?